added toggle header + alias command fixes
parent
7d05e4401e
commit
5baa447e46
1
go.sum
1
go.sum
|
|
@ -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=
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
alias:
|
||||||
|
dp: "apps.v1.deployments"
|
||||||
|
pe: ".v1.pods"
|
||||||
|
|
@ -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 ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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{})
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
if meta, ok := i.Object["metadata"].(map[string]interface{}); ok {
|
||||||
pp["name"] = meta["name"]
|
m.Name = meta["name"].(string)
|
||||||
}
|
}
|
||||||
pp["group"], pp["version"] = spec["group"], spec["version"]
|
m.Group, m.Version = spec["group"].(string), spec["version"].(string)
|
||||||
|
m.Namespaced = isNamespaced(spec["scope"].(string))
|
||||||
if names, ok := spec["names"].(map[string]interface{}); ok {
|
names, ok := spec["names"].(map[string]interface{})
|
||||||
pp["kind"] = names["kind"]
|
if !ok {
|
||||||
pp["singular"], pp["plural"] = names["singular"], names["plural"]
|
return
|
||||||
pp["aliases"] = names["shortNames"]
|
|
||||||
}
|
}
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return pp
|
|
||||||
|
func isNamespaced(scope string) bool {
|
||||||
|
return scope == "Namespaced"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,34 +18,35 @@ 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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
Hint{
|
||||||
Mnemonic: name,
|
Mnemonic: name,
|
||||||
Description: a[tcell.Key(k)].Description})
|
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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(header, 7, 1, false)
|
|
||||||
} else {
|
|
||||||
main.AddItem(a.indicator(), 1, 1, false)
|
main.AddItem(a.indicator(), 1, 1, false)
|
||||||
}
|
// main.AddItem(a.Cmd(), 3, 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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
if _, err := strconv.Atoi(h.Mnemonic); err != nil {
|
||||||
cell.SetTextColor(tcell.ColorDodgerBlue)
|
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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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{
|
vv[gvrs] = viewer{
|
||||||
gvr: gvr,
|
gvr: gvrs,
|
||||||
viewFn: listFunc(resource.NewCustomList(c, "", ff["group"].(string), ff["version"].(string), name)),
|
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.verbs = res.Verbs
|
|
||||||
cmd.gvr = gvr
|
|
||||||
viewRes[gvr] = cmd
|
|
||||||
aliases[strings.ToLower(res.Kind)] = gvr
|
|
||||||
aliases[res.Name] = gvr
|
|
||||||
aliases[res.SingularName] = gvr
|
|
||||||
for _, s := range res.ShortNames {
|
|
||||||
aliases[s] = gvr
|
|
||||||
}
|
}
|
||||||
|
cmd.namespaced = res.Namespaced
|
||||||
|
cmd.kind = res.Kind
|
||||||
|
cmd.verbs = res.Verbs
|
||||||
|
cmd.gvr = gvr.String()
|
||||||
|
vv[gvr.String()] = cmd
|
||||||
|
gvrStr := gvr.String()
|
||||||
|
aliases.Define(
|
||||||
|
strings.ToLower(res.Kind), gvrStr,
|
||||||
|
res.Name, gvrStr,
|
||||||
|
)
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue