Rel v0.40.8 (#3198)

* Update deprecated yaml.v2->v3

* [Fix] fix issue with yaml sanitization when [] or [xxx] are present

* Spring cleanup

* fix#3192

* Column Blow Reloaded

* Add ability to use alias when specifying custom views
mine
Fernand Galiana 2025-03-11 18:15:20 -06:00 committed by GitHub
parent 5e05221a26
commit 45c2137df8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 370 additions and 956 deletions

View File

@ -13,7 +13,7 @@ import (
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
) )
func infoCmd() *cobra.Command { func infoCmd() *cobra.Command {

View File

@ -20,7 +20,6 @@ import (
"github.com/derailed/k9s/internal/view" "github.com/derailed/k9s/internal/view"
"github.com/lmittmann/tint" "github.com/lmittmann/tint"
// "github.com/MatusOllah/slogcolor"
"github.com/mattn/go-colorable" "github.com/mattn/go-colorable"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
@ -134,6 +133,12 @@ func loadConfiguration() (*config.Config, error) {
k9sCfg := config.NewConfig(k8sCfg) k9sCfg := config.NewConfig(k8sCfg)
var errs error var errs error
conn, err := client.InitConnection(k8sCfg, slog.Default())
if err != nil {
errs = errors.Join(errs, err)
}
k9sCfg.SetConnection(conn)
if err := k9sCfg.Load(config.AppConfigFile, false); err != nil { if err := k9sCfg.Load(config.AppConfigFile, false); err != nil {
errs = errors.Join(errs, err) errs = errors.Join(errs, err)
} }
@ -143,10 +148,6 @@ func loadConfiguration() (*config.Config, error) {
errs = errors.Join(errs, err) errs = errors.Join(errs, err)
} }
conn, err := client.InitConnection(k8sCfg, slog.Default())
if err != nil {
errs = errors.Join(errs, err)
}
// Try to access server version if that fail. Connectivity issue? // Try to access server version if that fail. Connectivity issue?
if !conn.CheckConnectivity() { if !conn.CheckConnectivity() {
errs = errors.Join(errs, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName())) errs = errors.Join(errs, fmt.Errorf("cannot connect to context: %s", k9sCfg.K9s.ActiveContextName()))
@ -157,7 +158,7 @@ func loadConfiguration() (*config.Config, error) {
} else { } else {
slog.Info("✅ Kubernetes connectivity OK") slog.Info("✅ Kubernetes connectivity OK")
} }
k9sCfg.SetConnection(conn)
if err := k9sCfg.Save(false); err != nil { if err := k9sCfg.Save(false); err != nil {
slog.Error("K9s config save failed", slogs.Error, err) slog.Error("K9s config save failed", slogs.Error, err)
errs = errors.Join(errs, err) errs = errors.Join(errs, err)

3
go.mod
View File

@ -9,7 +9,6 @@ require (
github.com/anchore/syft v1.20.0 github.com/anchore/syft v1.20.0
github.com/atotto/clipboard v0.1.4 github.com/atotto/clipboard v0.1.4
github.com/cenkalti/backoff/v4 v4.3.0 github.com/cenkalti/backoff/v4 v4.3.0
github.com/derailed/popeye v0.11.3
github.com/derailed/tcell/v2 v2.3.1-rc.3 github.com/derailed/tcell/v2 v2.3.1-rc.3
github.com/derailed/tview v0.8.5 github.com/derailed/tview v0.8.5
github.com/fatih/color v1.18.0 github.com/fatih/color v1.18.0
@ -27,7 +26,6 @@ require (
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/text v0.23.0 golang.org/x/text v0.23.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
helm.sh/helm/v3 v3.17.1 helm.sh/helm/v3 v3.17.1
k8s.io/api v0.32.2 k8s.io/api v0.32.2
@ -266,7 +264,6 @@ require (
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/rubenv/sql-migrate v1.7.1 // indirect github.com/rubenv/sql-migrate v1.7.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/saferwall/pe v1.5.6 // indirect github.com/saferwall/pe v1.5.6 // indirect

11
go.sum
View File

@ -418,7 +418,6 @@ github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9Fqctt
github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -432,8 +431,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M=
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk=
github.com/derailed/popeye v0.11.3 h1:gQUp6zuSIRDBdyLS1Ln0nFs8FbQ+KGE+iQxe0w4Ug8M=
github.com/derailed/popeye v0.11.3/go.mod h1:HygqX7A8BwidorJjJUnWDZ5AvbxHIU7uRwXgOtn9GwY=
github.com/derailed/tcell/v2 v2.3.1-rc.3 h1:9s1fmyRcSPRlwr/C9tcpJKCujbrtmPpST6dcMUD2piY= github.com/derailed/tcell/v2 v2.3.1-rc.3 h1:9s1fmyRcSPRlwr/C9tcpJKCujbrtmPpST6dcMUD2piY=
github.com/derailed/tcell/v2 v2.3.1-rc.3/go.mod h1:nf68BEL8fjmXQHJT3xZjoZFs2uXOzyJcNAQqGUEMrFY= github.com/derailed/tcell/v2 v2.3.1-rc.3/go.mod h1:nf68BEL8fjmXQHJT3xZjoZFs2uXOzyJcNAQqGUEMrFY=
github.com/derailed/tview v0.8.5 h1:pogM/OnWlgDo6j4zyzdiIXh7E7+eT7D4CPfBnyaETug= github.com/derailed/tview v0.8.5 h1:pogM/OnWlgDo6j4zyzdiIXh7E7+eT7D4CPfBnyaETug=
@ -890,7 +887,6 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@ -900,8 +896,6 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw= github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw=
@ -1084,9 +1078,6 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rubenv/sql-migrate v1.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmiUq4= github.com/rubenv/sql-migrate v1.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmiUq4=
github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4= github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@ -1539,11 +1530,9 @@ golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View File

@ -54,11 +54,9 @@ func (c *Config) CallTimeout() time.Duration {
func (c *Config) RESTConfig() (*restclient.Config, error) { func (c *Config) RESTConfig() (*restclient.Config, error) {
cfg, err := c.clientConfig().ClientConfig() cfg, err := c.clientConfig().ClientConfig()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if c.proxy != nil { if c.proxy != nil {
cfg.Proxy = c.proxy cfg.Proxy = c.proxy
} }
@ -192,15 +190,11 @@ func (c *Config) GetContext(n string) (*api.Context, error) {
return nil, fmt.Errorf("getcontext - invalid context specified: %q", n) return nil, fmt.Errorf("getcontext - invalid context specified: %q", n)
} }
// SetProxy sets the proxy function.
func (c *Config) SetProxy(proxy func(*http.Request) (*url.URL, error)) { func (c *Config) SetProxy(proxy func(*http.Request) (*url.URL, error)) {
c.proxy = proxy c.proxy = proxy
} }
func (c *Config) WithProxy(proxy func(*http.Request) (*url.URL, error)) *Config {
c.SetProxy(proxy)
return c
}
// Contexts fetch all available contexts. // Contexts fetch all available contexts.
func (c *Config) Contexts() (map[string]*api.Context, error) { func (c *Config) Contexts() (map[string]*api.Context, error) {
cfg, err := c.RawConfig() cfg, err := c.RawConfig()

View File

@ -13,7 +13,7 @@ import (
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
) )
// Alias tracks shortname to GVR mappings. // Alias tracks shortname to GVR mappings.
@ -176,8 +176,6 @@ func (a *Aliases) loadDefaultAliases() {
a.declare("help", "h", "?") a.declare("help", "h", "?")
a.declare("quit", "q", "q!", "qa", "Q") a.declare("quit", "q", "q!", "qa", "Q")
a.declare("aliases", "alias", "a") a.declare("aliases", "alias", "a")
// !!BOZO!!
// a.declare("popeye", "pop")
a.declare("helm", "charts", "chart", "hm") a.declare("helm", "charts", "chart", "hm")
a.declare("dir", "d") a.declare("dir", "d")
a.declare("contexts", "context", "ctx") a.declare("contexts", "context", "ctx")
@ -202,10 +200,6 @@ func (a *Aliases) SaveAliases(path string) error {
if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil { if err := data.EnsureDirPath(path, data.DefaultDirMod); err != nil {
return err return err
} }
cfg, err := yaml.Marshal(a)
if err != nil {
return err
}
return os.WriteFile(path, cfg, data.DefaultFileMod) return data.SaveYAML(path, a)
} }

View File

@ -7,7 +7,7 @@ import (
"net/http" "net/http"
"os" "os"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
) )
// K9sBench the name of the benchmarks config file. // K9sBench the name of the benchmarks config file.

View File

@ -15,7 +15,7 @@ import (
"github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/view/cmd" "github.com/derailed/k9s/internal/view/cmd"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
) )
@ -289,14 +289,13 @@ func (c *Config) SaveFile(path string) error {
return err return err
} }
cfg, err := yaml.Marshal(c) if err := data.SaveYAML(path, c); err != nil {
if err != nil {
slog.Error("Unable to save K9s config file", slogs.Error, err) slog.Error("Unable to save K9s config file", slogs.Error, err)
return err return err
} }
slog.Info("[CONFIG] Saving K9s config to disk", slogs.Path, path) slog.Info("[CONFIG] Saving K9s config to disk", slogs.Path, path)
return os.WriteFile(path, cfg, data.DefaultFileMod) return nil
} }
// Validate the configuration. // Validate the configuration.

View File

@ -9,7 +9,7 @@ import (
"sync" "sync"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
"k8s.io/client-go/tools/clientcmd/api" "k8s.io/client-go/tools/clientcmd/api"
) )

View File

@ -14,7 +14,7 @@ import (
"github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
"k8s.io/client-go/tools/clientcmd/api" "k8s.io/client-go/tools/clientcmd/api"
) )
@ -74,12 +74,8 @@ func (d *Dir) Save(path string, c *Config) error {
if err := EnsureDirPath(path, DefaultDirMod); err != nil { if err := EnsureDirPath(path, DefaultDirMod); err != nil {
return err return err
} }
cfg, err := yaml.Marshal(c)
if err != nil {
return err
}
return os.WriteFile(path, cfg, DefaultFileMod) return SaveYAML(path, c)
} }
func (d *Dir) loadConfig(path string) (*Config, error) { func (d *Dir) loadConfig(path string) (*Config, error) {

View File

@ -12,7 +12,7 @@ import (
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/mock" "github.com/derailed/k9s/internal/config/mock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
) )

View File

@ -4,11 +4,14 @@
package data package data
import ( import (
"bytes"
"errors" "errors"
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"gopkg.in/yaml.v3"
) )
const envFGNodeShell = "K9S_FEATURE_GATE_NODE_SHELL" const envFGNodeShell = "K9S_FEATURE_GATE_NODE_SHELL"
@ -48,3 +51,28 @@ func EnsureFullPath(path string, mod os.FileMode) error {
return nil return nil
} }
// WriteYAML writes a yaml file to bytes.
func WriteYAML(content any) ([]byte, error) {
buff := bytes.NewBuffer(nil)
ec := yaml.NewEncoder(buff)
ec.SetIndent(2)
if err := ec.Encode(content); err != nil {
return nil, err
}
return buff.Bytes(), nil
}
// SaveYAML writes a yaml file to disk.
func SaveYAML(path string, content any) error {
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, DefaultFileMod)
if err != nil {
return err
}
ec := yaml.NewEncoder(f)
ec.SetIndent(2)
return ec.Encode(content)
}

View File

@ -59,15 +59,15 @@ func (n *Namespace) merge(old *Namespace) {
} }
// Validate validates a namespace is setup correctly. // Validate validates a namespace is setup correctly.
func (n *Namespace) Validate(c client.Connection) { func (n *Namespace) Validate(conn client.Connection) {
n.mx.RLock() n.mx.RLock()
defer n.mx.RUnlock() defer n.mx.RUnlock()
if c == nil || !c.IsValidNamespace(n.Active) { if conn == nil || !conn.IsValidNamespace(n.Active) {
return return
} }
for _, ns := range n.Favorites { for _, ns := range n.Favorites {
if !c.IsValidNamespace(ns) { if !conn.IsValidNamespace(ns) {
slog.Debug("Invalid favorite found", slog.Debug("Invalid favorite found",
slogs.Namespace, ns, slogs.Namespace, ns,
slogs.AllNS, n.isAllNamespaces(), slogs.AllNS, n.isAllNamespaces(),

View File

@ -12,7 +12,7 @@ import (
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
) )
// HotKeys represents a collection of plugins. // HotKeys represents a collection of plugins.

View File

@ -36,9 +36,6 @@ type K9s struct {
Logger Logger `json:"logger" yaml:"logger"` Logger Logger `json:"logger" yaml:"logger"`
Thresholds Threshold `json:"thresholds" yaml:"thresholds"` Thresholds Threshold `json:"thresholds" yaml:"thresholds"`
manualRefreshRate int manualRefreshRate int
manualHeadless *bool
manualLogoless *bool
manualCrumbsless *bool
manualReadOnly *bool manualReadOnly *bool
manualCommand *string manualCommand *string
manualScreenDumpDir *string manualScreenDumpDir *string
@ -293,9 +290,9 @@ func (k *K9s) Override(k9sFlags *Flags) {
k.manualRefreshRate = *k9sFlags.RefreshRate k.manualRefreshRate = *k9sFlags.RefreshRate
} }
k.manualHeadless = k9sFlags.Headless k.UI.manualHeadless = k9sFlags.Headless
k.manualLogoless = k9sFlags.Logoless k.UI.manualLogoless = k9sFlags.Logoless
k.manualCrumbsless = k9sFlags.Crumbsless k.UI.manualCrumbsless = k9sFlags.Crumbsless
if k9sFlags.ReadOnly != nil && *k9sFlags.ReadOnly { if k9sFlags.ReadOnly != nil && *k9sFlags.ReadOnly {
k.manualReadOnly = k9sFlags.ReadOnly k.manualReadOnly = k9sFlags.ReadOnly
} }
@ -309,7 +306,7 @@ func (k *K9s) Override(k9sFlags *Flags) {
// IsHeadless returns headless setting. // IsHeadless returns headless setting.
func (k *K9s) IsHeadless() bool { func (k *K9s) IsHeadless() bool {
if IsBoolSet(k.manualHeadless) { if IsBoolSet(k.UI.manualHeadless) {
return true return true
} }
@ -318,7 +315,7 @@ func (k *K9s) IsHeadless() bool {
// IsLogoless returns logoless setting. // IsLogoless returns logoless setting.
func (k *K9s) IsLogoless() bool { func (k *K9s) IsLogoless() bool {
if IsBoolSet(k.manualLogoless) { if IsBoolSet(k.UI.manualLogoless) {
return true return true
} }
@ -327,7 +324,7 @@ func (k *K9s) IsLogoless() bool {
// IsCrumbsless returns crumbsless setting. // IsCrumbsless returns crumbsless setting.
func (k *K9s) IsCrumbsless() bool { func (k *K9s) IsCrumbsless() bool {
if IsBoolSet(k.manualCrumbsless) { if IsBoolSet(k.UI.manualCrumbsless) {
return true return true
} }

View File

@ -66,17 +66,17 @@ func Test_k9sOverrides(t *testing.T) {
ReadOnly: false, ReadOnly: false,
NoExitOnCtrlC: false, NoExitOnCtrlC: false,
UI: UI{ UI: UI{
Headless: false, Headless: false,
Logoless: false, Logoless: false,
Crumbsless: false, Crumbsless: false,
manualHeadless: &true,
manualLogoless: &true,
manualCrumbsless: &true,
}, },
SkipLatestRevCheck: false, SkipLatestRevCheck: false,
DisablePodCounting: false, DisablePodCounting: false,
manualRefreshRate: 100, manualRefreshRate: 100,
manualReadOnly: &true, manualReadOnly: &true,
manualHeadless: &true,
manualLogoless: &true,
manualCrumbsless: &true,
manualCommand: &cmd, manualCommand: &cmd,
manualScreenDumpDir: &dir, manualScreenDumpDir: &dir,
}, },

View File

@ -4,6 +4,7 @@
package config package config
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
@ -16,7 +17,7 @@ import (
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
) )
const k9sPluginsDir = "k9s/plugins" const k9sPluginsDir = "k9s/plugins"
@ -88,10 +89,14 @@ func (p Plugins) loadPluginDir(dir string) error {
if err != nil { if err != nil {
errs = errors.Join(errs, err) errs = errors.Join(errs, err)
} }
d := yaml.NewDecoder(bytes.NewReader(fileContent))
d.KnownFields(true)
var plugin Plugin var plugin Plugin
if err = yaml.UnmarshalStrict(fileContent, &plugin); err != nil { if err = d.Decode(&plugin); err != nil {
var plugins Plugins var plugins Plugins
if err = yaml.UnmarshalStrict(fileContent, &plugins); err != nil { if err = d.Decode(&plugins); err != nil {
return fmt.Errorf("cannot parse %s into either a single plugin nor plugins: %w", fileName, err) return fmt.Errorf("cannot parse %s into either a single plugin nor plugins: %w", fileName, err)
} }
for name, plugin := range plugins.Plugins { for name, plugin := range plugins.Plugins {

View File

@ -11,7 +11,7 @@ import (
"github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/config/json"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
) )
// StyleListener represents a skin's listener. // StyleListener represents a skin's listener.

View File

@ -17,3 +17,9 @@ views:
- AGE - AGE
- NAME - NAME
- IP - IP
bozo:
columns:
- DUH
- BLAH
- BLEE

View File

@ -34,4 +34,8 @@ type UI struct {
// DefaultsToFullScreen toggles fullscreen on views like logs, yaml, details. // DefaultsToFullScreen toggles fullscreen on views like logs, yaml, details.
DefaultsToFullScreen bool `json:"defaultsToFullScreen" yaml:"defaultsToFullScreen"` DefaultsToFullScreen bool `json:"defaultsToFullScreen" yaml:"defaultsToFullScreen"`
manualHeadless *bool
manualLogoless *bool
manualCrumbsless *bool
} }

View File

@ -15,10 +15,11 @@ import (
"slices" "slices"
"strings" "strings"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data" "github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/config/json" "github.com/derailed/k9s/internal/config/json"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
) )
// ViewConfigListener represents a view config listener. // ViewConfigListener represents a view config listener.
@ -118,29 +119,65 @@ func (v *CustomView) Load(path string) error {
return nil return nil
} }
// AddListeners registers a new listener for various commands.
func (v *CustomView) AddListeners(l ViewConfigListener, cmds ...string) {
for _, cmd := range cmds {
if cmd != "" {
v.listeners[cmd] = l
}
}
v.fireConfigChanged()
}
// AddListener registers a new listener. // AddListener registers a new listener.
func (v *CustomView) AddListener(gvr string, l ViewConfigListener) { func (v *CustomView) AddListener(cmd string, l ViewConfigListener) {
v.listeners[gvr] = l v.listeners[cmd] = l
v.fireConfigChanged() v.fireConfigChanged()
} }
// RemoveListener unregister a listener. // RemoveListener unregister a listener.
func (v *CustomView) RemoveListener(gvr string) { func (v *CustomView) RemoveListener(l ViewConfigListener) {
delete(v.listeners, gvr) for k, list := range v.listeners {
} if list == l {
delete(v.listeners, k)
func (v *CustomView) fireConfigChanged() {
for gvr, list := range v.listeners {
if vs := v.getVS(gvr, list.GetNamespace()); vs == nil {
list.ViewSettingsChanged(nil)
} else {
slog.Debug("Reloading custom view settings", slogs.GVR, gvr)
list.ViewSettingsChanged(vs)
} }
} }
} }
func (v *CustomView) fireConfigChanged() {
cmds := slices.Collect(maps.Keys(v.listeners))
slices.SortFunc(cmds, func(a, b string) int {
switch {
case strings.Contains(a, "/") && !strings.Contains(b, "/"):
return 1
case !strings.Contains(a, "/") && strings.Contains(b, "/"):
return -1
default:
return strings.Compare(a, b)
}
})
type tuple struct {
cmd string
vs *ViewSetting
}
var victim tuple
for _, cmd := range cmds {
if vs := v.getVS(cmd, v.listeners[cmd].GetNamespace()); vs != nil {
slog.Debug("Reloading custom view settings", slogs.Command, cmd)
victim = tuple{cmd, vs}
break
}
victim = tuple{cmd, nil}
}
if victim.cmd != "" {
v.listeners[victim.cmd].ViewSettingsChanged(victim.vs)
}
}
func (v *CustomView) getVS(gvr, ns string) *ViewSetting { func (v *CustomView) getVS(gvr, ns string) *ViewSetting {
if client.IsAllNamespaces(ns) {
ns = client.NamespaceAll
}
k := gvr k := gvr
kk := slices.Collect(maps.Keys(v.Views)) kk := slices.Collect(maps.Keys(v.Views))
slices.SortFunc(kk, func(s1, s2 string) int { slices.SortFunc(kk, func(s1, s2 string) int {

View File

@ -17,6 +17,10 @@ func TestCustomView_getVS(t *testing.T) {
}{ }{
"empty": {}, "empty": {},
"miss": {
gvr: "zorg",
},
"gvr": { "gvr": {
gvr: "v1/pods", gvr: "v1/pods",
e: &ViewSetting{ e: &ViewSetting{
@ -40,6 +44,13 @@ func TestCustomView_getVS(t *testing.T) {
}, },
}, },
"alias": {
gvr: "bozo",
e: &ViewSetting{
Columns: []string{"DUH", "BLAH", "BLEE"},
},
},
"toast-no-ns": { "toast-no-ns": {
gvr: "v1/pods", gvr: "v1/pods",
ns: "zorg", ns: "zorg",

View File

@ -10,9 +10,9 @@ import (
"os" "os"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/render/helm" "github.com/derailed/k9s/internal/render/helm"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"gopkg.in/yaml.v2"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
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"
@ -83,7 +83,7 @@ func (h *HelmChart) GetValues(path string, allValues bool) ([]byte, error) {
return nil, err return nil, err
} }
return yaml.Marshal(resp) return data.WriteYAML(resp)
} }
// Describe returns the chart notes. // Describe returns the chart notes.

View File

@ -9,13 +9,13 @@ import (
"strconv" "strconv"
"strings" "strings"
"gopkg.in/yaml.v2"
"helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/action"
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"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/render/helm" "github.com/derailed/k9s/internal/render/helm"
) )
@ -126,10 +126,14 @@ func (h *HelmHistory) GetValues(path string, allValues bool) ([]byte, error) {
return nil, fmt.Errorf("expected helm.ReleaseRes, but got %T", rel) return nil, fmt.Errorf("expected helm.ReleaseRes, but got %T", rel)
} }
var content any
if allValues { if allValues {
return yaml.Marshal(resp.Release.Chart.Values) content = resp.Release.Chart.Values
} else {
content = resp.Release.Config
} }
return yaml.Marshal(resp.Release.Config)
return data.WriteYAML(content)
} }
func (h *HelmHistory) Rollback(_ context.Context, path, rev string) error { func (h *HelmHistory) Rollback(_ context.Context, path, rev string) error {

View File

@ -1,142 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dao
// !!BOZO!! Popeye
// import (
// "bytes"
// "context"
// "encoding/json"
// "errors"
// "fmt"
// "os"
// "path/filepath"
// "sort"
// "time"
// "github.com/derailed/k9s/internal"
// "github.com/derailed/k9s/internal/client"
// cfg "github.com/derailed/k9s/internal/config"
// "github.com/derailed/k9s/internal/render"
// "github.com/derailed/popeye/pkg"
// "github.com/derailed/popeye/pkg/config"
// "github.com/derailed/popeye/types"
// "k8s.io/apimachinery/pkg/runtime"
// )
// var _ Accessor = (*Popeye)(nil)
// // Popeye tracks cluster sanitization.
// type Popeye struct {
// NonResource
// }
// // NewPopeye returns a new set of aliases.
// func NewPopeye(f Factory) *Popeye {
// a := Popeye{}
// a.Init(f, client.NewGVR("popeye"))
// return &a
// }
// type readWriteCloser struct {
// *bytes.Buffer
// }
// // Close close read stream.
// func (readWriteCloser) Close() error {
// return nil
// }
// // List returns a collection of aliases.
// func (p *Popeye) List(ctx context.Context, ns string) ([]runtime.Object, error) {
// defer func(t time.Time) {
// log.Debug().Msgf("Popeye -- Elapsed %v", time.Since(t))
// if err := recover(); err != nil {
// log.Debug().Msgf("POPEYE DIED!")
// }
// }(time.Now())
// flags, js := config.NewFlags(), "json"
// flags.Output = &js
// flags.ActiveNamespace = &ns
// if report, ok := ctx.Value(internal.KeyPath).(string); ok && report != "" {
// ns, n := client.Namespaced(report)
// sections := []string{n}
// flags.Sections = &sections
// flags.ActiveNamespace = &ns
// }
// spinach := filepath.Join(cfg.AppConfigDir, "spinach.yaml")
// if c, err := p.getFactory().Client().Config().CurrentContextName(); err == nil {
// spinach = filepath.Join(cfg.AppConfigDir, fmt.Sprintf("%s_spinach.yaml", c))
// }
// if _, err := os.Stat(spinach); err == nil {
// flags.Spinach = &spinach
// }
// popeye, err := pkg.NewPopeye(flags, &log.Logger)
// if err != nil {
// return nil, err
// }
// popeye.SetFactory(newPopeyeFactory(p.Factory))
// if err = popeye.Init(); err != nil {
// return nil, err
// }
// buff := readWriteCloser{Buffer: bytes.NewBufferString("")}
// popeye.SetOutputTarget(buff)
// if _, _, err = popeye.Sanitize(); err != nil {
// log.Error().Err(err).Msgf("BOOM %#v", *flags.Sections)
// return nil, err
// }
// var b render.Builder
// if err = json.Unmarshal(buff.Bytes(), &b); err != nil {
// return nil, err
// }
// oo := make([]runtime.Object, 0, len(b.Report.Sections))
// sort.Sort(b.Report.Sections)
// for _, s := range b.Report.Sections {
// s.Tally.Count = len(s.Outcome)
// if s.Tally.Sum() > 0 {
// oo = append(oo, s)
// }
// }
// return oo, nil
// }
// // Get retrieves a resource.
// func (p *Popeye) Get(_ context.Context, _ string) (runtime.Object, error) {
// return nil, errors.New("NYI!!")
// }
// // ----------------------------------------------------------------------------
// // Helpers...
// type popFactory struct {
// Factory
// }
// var _ types.Factory = (*popFactory)(nil)
// func newPopeyeFactory(f Factory) *popFactory {
// return &popFactory{Factory: f}
// }
// func (p *popFactory) Client() types.Connection {
// return &popeyeConnection{Connection: p.Factory.Client()}
// }
// type popeyeConnection struct {
// client.Connection
// }
// var _ types.Connection = (*popeyeConnection)(nil)
// func (c *popeyeConnection) Config() types.Config {
// return c.Connection.Config()
// }

View File

@ -242,21 +242,6 @@ func loadK9s(m ResourceMetas) {
Verbs: []string{}, Verbs: []string{},
Categories: []string{k9sCat}, Categories: []string{k9sCat},
} }
m[client.NewGVR("popeye")] = metav1.APIResource{
Name: "popeye",
Kind: "Popeye",
SingularName: "popeye",
Namespaced: true,
Verbs: []string{},
Categories: []string{k9sCat},
}
m[client.NewGVR("sanitizer")] = metav1.APIResource{
Name: "sanitizer",
Kind: "Sanitizer",
SingularName: "sanitizer",
Verbs: []string{},
Categories: []string{k9sCat},
}
m[client.NewGVR("contexts")] = metav1.APIResource{ m[client.NewGVR("contexts")] = metav1.APIResource{
Name: "contexts", Name: "contexts",
Kind: "Contexts", Kind: "Contexts",

View File

@ -9,6 +9,7 @@ import (
"testing" "testing"
"github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -291,14 +292,15 @@ func makeC(n string) c {
return c{name: n} return c{name: n}
} }
func (c) InCmdMode() bool { return false } func (c) InCmdMode() bool { return false }
func (c c) Name() string { return c.name } func (c c) Name() string { return c.name }
func (c c) Hints() model.MenuHints { return nil } func (c) SetCommand(*cmd.Interpreter) {}
func (c c) HasFocus() bool { return false } func (c) Hints() model.MenuHints { return nil }
func (c c) ExtraHints() map[string]string { return nil } func (c) HasFocus() bool { return false }
func (c c) Draw(tcell.Screen) {} func (c) ExtraHints() map[string]string { return nil }
func (c c) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) { return nil } func (c) Draw(tcell.Screen) {}
func (c c) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { func (c) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) { return nil }
func (c) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
return nil return nil
} }
func (c c) SetRect(int, int, int, int) {} func (c c) SetRect(int, int, int, int) {}

View File

@ -10,6 +10,7 @@ import (
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/model1" "github.com/derailed/k9s/internal/model1"
"github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/sahilm/fuzzy" "github.com/sahilm/fuzzy"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -93,6 +94,13 @@ type Component interface {
Hinter Hinter
Commander Commander
Filterer Filterer
Viewer
}
// Viewer represents a resource viewer.
type Viewer interface {
// SetCommand sets the current command.
SetCommand(*cmd.Interpreter)
} }
type Filterer interface { type Filterer interface {

View File

@ -48,6 +48,7 @@ type Renderer interface {
// ColorerFunc returns a row colorer function. // ColorerFunc returns a row colorer function.
ColorerFunc() ColorerFunc ColorerFunc() ColorerFunc
// SetViewSetting sets custom view settings if any.
SetViewSetting(vs *config.ViewSetting) SetViewSetting(vs *config.ViewSetting)
} }

View File

@ -42,6 +42,7 @@ func (b *Base) doHeader(dh model1.Header) model1.Header {
return b.specs.Header(dh) return b.specs.Header(dh)
} }
// SetViewSetting sets custom view settings if any.
func (b *Base) SetViewSetting(vs *config.ViewSetting) { func (b *Base) SetViewSetting(vs *config.ViewSetting) {
var cols []string var cols []string
b.vs = vs b.vs = vs

View File

@ -1,195 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package render
import "github.com/derailed/popeye/pkg/config"
// !!BOZO!! Popeye
// // Popeye renders a sanitizer to screen.
// type Popeye struct {
// Base
// }
// // ColorerFunc colors a resource row.
// func (Popeye) ColorerFunc() ColorerFunc {
// return func(ns string, h Header, re *model1.RowEvent) tcell.Color {
// c := DefaultColorer(ns, h, re)
// warnCol := h.IndexOf("WARNING", true)
// status, _ := strconv.Atoi(strings.TrimSpace(re.Row.Fields[warnCol]))
// if status > 0 {
// c = tcell.ColorOrange
// }
// errCol := h.IndexOf("ERROR", true)
// status, _ = strconv.Atoi(strings.TrimSpace(re.Row.Fields[errCol]))
// if status > 0 {
// c = ErrColor
// }
// return c
// }
// }
// // Header returns a header row.
// func (Popeye) Header(ns string) model1.Header {
// return model1.Header{
// model1.HeaderColumn{Name: "RESOURCE"},
// model1.HeaderColumn{Name: "SCORE%", Align: tview.AlignRight},
// model1.HeaderColumn{Name: "SCANNED", Align: tview.AlignRight},
// model1.HeaderColumn{Name: "ERROR", Align: tview.AlignRight},
// model1.HeaderColumn{Name: "WARNING", Align: tview.AlignRight},
// model1.HeaderColumn{Name: "INFO", Align: tview.AlignRight},
// model1.HeaderColumn{Name: "OK", Align: tview.AlignRight},
// }
// }
// // Render renders a K8s resource to screen.
// func (Popeye) Render(o interface{}, ns string, r *model1.Row) error {
// s, ok := o.(Section)
// if !ok {
// return fmt.Errorf("expected Section, but got %T", o)
// }
// r.ID = client.FQN(ns, s.Title)
// r.Fields = append(r.Fields,
// s.Title,
// strconv.Itoa(s.Tally.Score()),
// strconv.Itoa(s.Tally.OK+s.Tally.Info+s.Tally.Warning+s.Tally.Error),
// strconv.Itoa(s.Tally.Error),
// strconv.Itoa(s.Tally.Warning),
// strconv.Itoa(s.Tally.Info),
// strconv.Itoa(s.Tally.OK),
// )
// return nil
// }
// // ----------------------------------------------------------------------------
// // Helpers...
type (
// // Builder represents a popeye report.
// Builder struct {
// Report Report `json:"popeye" yaml:"popeye"`
// }
// // Report represents the output of a sanitization pass.
// Report struct {
// Score int `json:"score" yaml:"score"`
// Grade string `json:"grade" yaml:"grade"`
// Sections Sections `json:"sanitizers,omitempty" yaml:"sanitizers,omitempty"`
// }
// Sections represents a collection of sections.
Sections []Section
// Section represents a sanitizer pass.
Section struct {
Title string `json:"sanitizer" yaml:"sanitizer"`
GVR string `yaml:"gvr" json:"gvr"`
Tally *Tally `json:"tally" yaml:"tally"`
Outcome Outcome `json:"issues,omitempty" yaml:"issues,omitempty"`
}
// Outcome represents a classification of reports outcome.
Outcome map[string]Issues
// Issues represents a collection of issues.
Issues []Issue
// Issue represents a sanitization issue.
Issue struct {
Group string `yaml:"group" json:"group"`
GVR string `yaml:"gvr" json:"gvr"`
Level config.Level `yaml:"level" json:"level"`
Message string `yaml:"message" json:"message"`
}
// Tally tracks a section scores.
Tally struct {
OK, Info, Warning, Error int
Count int
}
)
// // Sum sums up tally counts.
// func (t *Tally) Sum() int {
// return t.OK + t.Info + t.Warning + t.Error
// }
// // Score returns the overall sections score in percent.
// func (t *Tally) Score() int {
// oks := t.OK + t.Info
// return toPerc(float64(oks), float64(oks+t.Warning+t.Error))
// }
// func toPerc(v1, v2 float64) int {
// if v2 == 0 {
// return 0
// }
// return int(math.Floor((v1 / v2) * 100))
// }
// // Len returns a section length.
// func (s Sections) Len() int {
// return len(s)
// }
// // Swap swaps values.
// func (s Sections) Swap(i, j int) {
// s[i], s[j] = s[j], s[i]
// }
// // Less compares section scores.
// func (s Sections) Less(i, j int) bool {
// t1, t2 := s[i].Tally, s[j].Tally
// return t1.Score() < t2.Score()
// }
// // GetObjectKind returns a schema object.
// func (Section) GetObjectKind() schema.ObjectKind {
// return nil
// }
// // DeepCopyObject returns a container copy.
// func (s Section) DeepCopyObject() runtime.Object {
// return s
// }
// // MaxSeverity gather the max severity in a collection of issues.
// func (s Section) MaxSeverity() config.Level {
// max := config.OkLevel
// for _, issues := range s.Outcome {
// m := issues.MaxSeverity()
// if m > max {
// max = m
// }
// }
// return max
// }
// // MaxSeverity gather the max severity in a collection of issues.
// func (i Issues) MaxSeverity() config.Level {
// max := config.OkLevel
// for _, is := range i {
// if is.Level > max {
// max = is.Level
// }
// }
// return max
// }
// // CountSeverity counts severity level instances.
// func (i Issues) CountSeverity(l config.Level) int {
// var count int
// for _, is := range i {
// if is.Level == l {
// count++
// }
// }
// return count
// }

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package render
// Level tracks lint check level.
type Level int
const (
// OkLevel denotes no linting issues.
OkLevel Level = iota
// InfoLevel denotes FIY linting issues.
InfoLevel
// WarnLevel denotes a warning issue.
WarnLevel
// ErrorLevel denotes a serious issue.
ErrorLevel
)
type (
// Sections represents a collection of sections.
Sections []Section
// Section represents a sanitizer pass.
Section struct {
Title string `json:"sanitizer" yaml:"sanitizer"`
GVR string `yaml:"gvr" json:"gvr"`
Outcome Outcome `json:"issues,omitempty" yaml:"issues,omitempty"`
}
// Outcome represents a classification of reports outcome.
Outcome map[string]Issues
// Issues represents a collection of issues.
Issues []Issue
// Issue represents a sanitization issue.
Issue struct {
Group string `yaml:"group" json:"group"`
GVR string `yaml:"gvr" json:"gvr"`
Level Level `yaml:"level" json:"level"`
Message string `yaml:"message" json:"message"`
}
)

View File

@ -32,11 +32,19 @@ type synchronizer interface {
type Configurator struct { type Configurator struct {
Config *config.Config Config *config.Config
Styles *config.Styles Styles *config.Styles
CustomView *config.CustomView customView *config.CustomView
BenchFile string BenchFile string
skinFile string skinFile string
} }
func (c *Configurator) CustomView() *config.CustomView {
if c.customView == nil {
c.customView = config.NewCustomView()
}
return c.customView
}
// HasSkin returns true if a skin file was located. // HasSkin returns true if a skin file was located.
func (c *Configurator) HasSkin() bool { func (c *Configurator) HasSkin() bool {
return c.skinFile != "" return c.skinFile != ""
@ -82,13 +90,9 @@ func (c *Configurator) CustomViewsWatcher(ctx context.Context, s synchronizer) e
// RefreshCustomViews load view configuration changes. // RefreshCustomViews load view configuration changes.
func (c *Configurator) RefreshCustomViews() error { func (c *Configurator) RefreshCustomViews() error {
if c.CustomView == nil { c.CustomView().Reset()
c.CustomView = config.NewCustomView()
} else {
c.CustomView.Reset()
}
return c.CustomView.Load(config.AppViewsFile) return c.CustomView().Load(config.AppViewsFile)
} }
// SkinsDirWatcher watches for skin directory file changes. // SkinsDirWatcher watches for skin directory file changes.

View File

@ -11,6 +11,7 @@ import (
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -39,23 +40,24 @@ func makeComponent(n string) c {
return c{name: n} return c{name: n}
} }
func (c) InCmdMode() bool { return false } func (c) SetCommand(*cmd.Interpreter) {}
func (c c) HasFocus() bool { return true } func (c) InCmdMode() bool { return false }
func (c c) Hints() model.MenuHints { return nil } func (c) HasFocus() bool { return true }
func (c c) ExtraHints() map[string]string { return nil } func (c) Hints() model.MenuHints { return nil }
func (c c) Name() string { return c.name } func (c) ExtraHints() map[string]string { return nil }
func (c c) Draw(tcell.Screen) {} func (c c) Name() string { return c.name }
func (c c) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) { return nil } func (c) Draw(tcell.Screen) {}
func (c c) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { func (c) InputHandler() func(*tcell.EventKey, func(tview.Primitive)) { return nil }
func (c) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
return nil return nil
} }
func (c c) SetRect(int, int, int, int) {} func (c) SetRect(int, int, int, int) {}
func (c c) GetRect() (int, int, int, int) { return 0, 0, 0, 0 } func (c) GetRect() (int, int, int, int) { return 0, 0, 0, 0 }
func (c c) GetFocusable() tview.Focusable { return c } func (c c) GetFocusable() tview.Focusable { return c }
func (c c) Focus(func(tview.Primitive)) {} func (c) Focus(func(tview.Primitive)) {}
func (c c) Blur() {} func (c) Blur() {}
func (c c) Start() {} func (c) Start() {}
func (c c) Stop() {} func (c) Stop() {}
func (c c) Init(context.Context) error { return nil } func (c) Init(context.Context) error { return nil }
func (c c) SetFilter(string) {} func (c) SetFilter(string) {}
func (c c) SetLabelFilter(map[string]string) {} func (c) SetLabelFilter(map[string]string) {}

View File

@ -54,6 +54,7 @@ type Table struct {
ctx context.Context ctx context.Context
mx sync.RWMutex mx sync.RWMutex
readOnly bool readOnly bool
noIcon bool
} }
// NewTable returns a new table view. // NewTable returns a new table view.
@ -72,6 +73,15 @@ func NewTable(gvr client.GVR) *Table {
} }
} }
// SetNoIcon toggles no icon mode.
func (t *Table) SetNoIcon(b bool) {
t.mx.Lock()
defer t.mx.Unlock()
t.noIcon = b
}
// SetReadOnly toggles read-only mode.
func (t *Table) SetReadOnly(ro bool) { func (t *Table) SetReadOnly(ro bool) {
t.mx.Lock() t.mx.Lock()
defer t.mx.Unlock() defer t.mx.Unlock()
@ -114,7 +124,8 @@ func (t *Table) getMSort() bool {
return t.manualSort return t.manualSort
} }
func (t *Table) setViewSetting(vs *config.ViewSetting) bool { // SetViewSetting sets custom view config is present.
func (t *Table) SetViewSetting(vs *config.ViewSetting) bool {
t.mx.Lock() t.mx.Lock()
defer t.mx.Unlock() defer t.mx.Unlock()
@ -127,7 +138,8 @@ func (t *Table) setViewSetting(vs *config.ViewSetting) bool {
return false return false
} }
func (t *Table) getViewSetting() *config.ViewSetting { // GetViewSetting return current view settings if any.
func (t *Table) GetViewSetting() *config.ViewSetting {
t.mx.RLock() t.mx.RLock()
defer t.mx.RUnlock() defer t.mx.RUnlock()
@ -161,7 +173,7 @@ func (t *Table) GVR() client.GVR { return t.gvr }
// ViewSettingsChanged notifies listener the view configuration changed. // ViewSettingsChanged notifies listener the view configuration changed.
func (t *Table) ViewSettingsChanged(vs *config.ViewSetting) { func (t *Table) ViewSettingsChanged(vs *config.ViewSetting) {
if t.setViewSetting(vs) { if t.SetViewSetting(vs) {
if vs == nil { if vs == nil {
if !t.getMSort() && !t.sortCol.IsSet() { if !t.getMSort() && !t.sortCol.IsSet() {
t.setSortCol(model1.SortColumn{}) t.setSortCol(model1.SortColumn{})
@ -294,7 +306,7 @@ func (t *Table) doUpdate(data *model1.TableData) *model1.TableData {
t.actions.Delete(KeyShiftP) t.actions.Delete(KeyShiftP)
} }
t.setSortCol(data.ComputeSortCol(t.getViewSetting(), t.getSortCol(), t.getMSort())) t.setSortCol(data.ComputeSortCol(t.GetViewSetting(), t.getSortCol(), t.getMSort()))
return data return data
} }
@ -537,9 +549,12 @@ func (t *Table) styleTitle() string {
var title string var title string
if ns == client.ClusterScope { if ns == client.ClusterScope {
title = SkinTitle(fmt.Sprintf(TitleFmt, ROIndicator(t.readOnly), t.gvr, render.AsThousands(rc)), t.styles.Frame()) title = SkinTitle(fmt.Sprintf(TitleFmt, t.gvr, render.AsThousands(rc)), t.styles.Frame())
} else { } else {
title = SkinTitle(fmt.Sprintf(NSTitleFmt, ROIndicator(t.readOnly), t.gvr, ns, render.AsThousands(rc)), t.styles.Frame()) title = SkinTitle(fmt.Sprintf(NSTitleFmt, t.gvr, ns, render.AsThousands(rc)), t.styles.Frame())
}
if ic := ROIndicator(t.readOnly, t.noIcon); ic != "" {
title = " " + ic + title
} }
buff := t.cmdBuff.GetText() buff := t.cmdBuff.GetText()
@ -557,10 +572,14 @@ func (t *Table) styleTitle() string {
} }
// ROIndicator returns an icon showing whether the session is in readonly mode or not. // ROIndicator returns an icon showing whether the session is in readonly mode or not.
func ROIndicator(ro bool) string { func ROIndicator(ro, noIC bool) string {
if ro { if noIC {
return LockedIC return ""
} }
return UnlockedIC if ro {
return lockedIC
}
return unlockedIC
} }

View File

@ -23,10 +23,10 @@ const (
SearchFmt = "<[filter:bg:r]/%s[fg:bg:-]> " SearchFmt = "<[filter:bg:r]/%s[fg:bg:-]> "
// NSTitleFmt represents a namespaced view title. // NSTitleFmt represents a namespaced view title.
NSTitleFmt = " %s [fg:bg:b]%s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] " NSTitleFmt = " [fg:bg:b]%s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] "
// TitleFmt represents a standard view title. // TitleFmt represents a standard view title.
TitleFmt = " %s [fg:bg:b]%s[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] " TitleFmt = " [fg:bg:b]%s[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] "
descIndicator = "↓" descIndicator = "↓"
ascIndicator = "↑" ascIndicator = "↑"

View File

@ -16,11 +16,8 @@ import (
) )
const ( const (
// UnlockedIC represents an unlocked icon. unlockedIC = "🔓"
UnlockedIC = "🔓" lockedIC = "🔒"
// LockedIC represents a locked icon.
LockedIC = "🔒"
) )
// Namespaceable represents a namespaceable model. // Namespaceable represents a namespaceable model.

View File

@ -22,6 +22,7 @@ import (
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/k9s/internal/ui/dialog"
"github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@ -58,6 +59,12 @@ func (b *Browser) getUpdating() bool {
return b.updating return b.updating
} }
// SetCommand sets the current command.
func (b *Browser) SetCommand(cmd *cmd.Interpreter) {
b.GetTable().SetCommand(cmd)
//b.Table.SetViewSetting(b.app.CustomView().VSFor(cmd)
}
// Init watches all running pods in given namespace. // Init watches all running pods in given namespace.
func (b *Browser) Init(ctx context.Context) error { func (b *Browser) Init(ctx context.Context) error {
var err error var err error
@ -85,6 +92,7 @@ func (b *Browser) Init(ctx context.Context) error {
b.app.CmdBuff().Reset() b.app.CmdBuff().Reset()
} }
b.Table.SetReadOnly(b.app.Config.IsReadOnly()) b.Table.SetReadOnly(b.app.Config.IsReadOnly())
b.Table.SetNoIcon(b.app.Config.K9s.UI.NoIcons)
b.bindKeys(b.Actions()) b.bindKeys(b.Actions())
for _, f := range b.bindKeysFn { for _, f := range b.bindKeysFn {

View File

@ -5,6 +5,8 @@ package cmd
import ( import (
"strings" "strings"
"github.com/derailed/k9s/internal/client"
) )
// Interpreter tracks user prompt input. // Interpreter tracks user prompt input.
@ -218,7 +220,7 @@ func (c *Interpreter) FuzzyArg() (string, bool) {
func (c *Interpreter) NSArg() (string, bool) { func (c *Interpreter) NSArg() (string, bool) {
ns, ok := c.args[nsKey] ns, ok := c.args[nsKey]
return ns, ok && ns != "" return ns, ok && ns != client.BlankNamespace
} }
// HasContext returns the current context if any. // HasContext returns the current context if any.

View File

@ -329,7 +329,7 @@ func (c *Command) exec(p *cmd.Interpreter, gvr client.GVR, comp model.Component,
slog.Error("Failure detected during command exec", slogs.Error, e) slog.Error("Failure detected during command exec", slogs.Error, e)
c.app.Content.Dump() c.app.Content.Dump()
slog.Debug("Dumping history buffer", slogs.CmdHist, c.app.cmdHistory.List()) slog.Debug("Dumping history buffer", slogs.CmdHist, c.app.cmdHistory.List())
slog.Error("Dumping stack", slogs.Stack, debug.Stack()) slog.Error("Dumping stack", slogs.Stack, string(debug.Stack()))
p := cmd.NewInterpreter("pod") p := cmd.NewInterpreter("pod")
cmds := c.app.cmdHistory.List() cmds := c.app.cmdHistory.List()
@ -344,6 +344,8 @@ func (c *Command) exec(p *cmd.Interpreter, gvr client.GVR, comp model.Component,
if comp == nil { if comp == nil {
return fmt.Errorf("no component found for %s", gvr) return fmt.Errorf("no component found for %s", gvr)
} }
comp.SetCommand(p)
c.app.Flash().Infof("Viewing %s...", gvr) c.app.Flash().Infof("Viewing %s...", gvr)
if clearStack { if clearStack {
cmd := contextRX.ReplaceAllString(p.GetLine(), "") cmd := contextRX.ReplaceAllString(p.GetLine(), "")
@ -352,10 +354,10 @@ func (c *Command) exec(p *cmd.Interpreter, gvr client.GVR, comp model.Component,
if err := c.app.inject(comp, clearStack); err != nil { if err := c.app.inject(comp, clearStack); err != nil {
return err return err
} }
if pushCmd { if pushCmd {
c.app.cmdHistory.Push(p.GetLine()) c.app.cmdHistory.Push(p.GetLine())
} }
slog.Debug("History", slogs.Stack, c.app.cmdHistory.List())
return return
} }

View File

@ -12,6 +12,7 @@ import (
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/sahilm/fuzzy" "github.com/sahilm/fuzzy"
@ -58,6 +59,7 @@ func NewDetails(app *App, title, subject, contentType string, searchable bool) *
return &d return &d
} }
func (d *Details) SetCommand(*cmd.Interpreter) {}
func (d *Details) SetFilter(string) {} func (d *Details) SetFilter(string) {}
func (d *Details) SetLabelFilter(map[string]string) {} func (d *Details) SetLabelFilter(map[string]string) {}

View File

@ -14,6 +14,7 @@ import (
"github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
) )
@ -43,8 +44,9 @@ func NewHelp(app *App) *Help {
} }
} }
func (h *Help) SetFilter(string) {} func (*Help) SetCommand(*cmd.Interpreter) {}
func (h *Help) SetLabelFilter(map[string]string) {} func (*Help) SetFilter(string) {}
func (*Help) SetLabelFilter(map[string]string) {}
// Init initializes the component. // Init initializes the component.
func (h *Help) Init(ctx context.Context) error { func (h *Help) Init(ctx context.Context) error {

View File

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"os" "os"
"regexp"
"strconv" "strconv"
"strings" "strings"
@ -55,8 +56,10 @@ func clipboardWrite(text string) error {
return nil return nil
} }
var bracketRX = regexp.MustCompile(`\[(.+)\[\]`)
func sanitizeEsc(s string) string { func sanitizeEsc(s string) string {
return strings.ReplaceAll(s, "[]", "]") return bracketRX.ReplaceAllString(s, `[$1]`)
} }
func cpCmd(flash *model.Flash, v *tview.TextView) func(*tcell.EventKey) *tcell.EventKey { func cpCmd(flash *model.Flash, v *tview.TextView) func(*tcell.EventKey) *tcell.EventKey {

View File

@ -327,3 +327,26 @@ func Test_linesWithRegions(t *testing.T) {
}) })
} }
} }
func Test_sanitizeEsc(t *testing.T) {
uu := map[string]struct {
s string
e string
}{
"empty": {},
"empty-brackets": {
s: "[]",
e: "[]",
},
"tag": {
s: "[fred[]",
e: "[fred]",
},
}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, sanitizeEsc(u.s))
})
}
}

View File

@ -15,6 +15,7 @@ import (
"github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/sahilm/fuzzy" "github.com/sahilm/fuzzy"
@ -61,6 +62,7 @@ func NewLiveView(app *App, title string, m model.ResourceViewer) *LiveView {
return &v return &v
} }
func (v *LiveView) SetCommand(*cmd.Interpreter) {}
func (v *LiveView) SetFilter(string) {} func (v *LiveView) SetFilter(string) {}
func (v *LiveView) SetLabelFilter(map[string]string) {} func (v *LiveView) SetLabelFilter(map[string]string) {}

View File

@ -22,6 +22,7 @@ import (
"github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
) )
@ -63,8 +64,9 @@ func NewLog(gvr client.GVR, opts *dao.LogOptions) *Log {
return &l return &l
} }
func (l *Log) SetFilter(string) {} func (*Log) SetCommand(*cmd.Interpreter) {}
func (l *Log) SetLabelFilter(map[string]string) {} func (*Log) SetFilter(string) {}
func (*Log) SetLabelFilter(map[string]string) {}
// Init initializes the viewer. // Init initializes the viewer.
func (l *Log) Init(ctx context.Context) (err error) { func (l *Log) Init(ctx context.Context) (err error) {

View File

@ -8,6 +8,7 @@ import (
"github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
) )
@ -27,6 +28,7 @@ func NewPicker() *Picker {
} }
} }
func (p *Picker) SetCommand(*cmd.Interpreter) {}
func (p *Picker) SetFilter(string) {} func (p *Picker) SetFilter(string) {}
func (p *Picker) SetLabelFilter(map[string]string) {} func (p *Picker) SetLabelFilter(map[string]string) {}

View File

@ -21,6 +21,7 @@ import (
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/tchart" "github.com/derailed/k9s/internal/tchart"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/view/cmd"
) )
// Graphable represents a graphic component. // Graphable represents a graphic component.
@ -77,6 +78,7 @@ func NewPulse(gvr client.GVR) ResourceViewer {
} }
} }
func (p *Pulse) SetCommand(*cmd.Interpreter) {}
func (p *Pulse) SetFilter(string) {} func (p *Pulse) SetFilter(string) {}
func (p *Pulse) SetLabelFilter(map[string]string) {} func (p *Pulse) SetLabelFilter(map[string]string) {}

View File

@ -90,9 +90,6 @@ func miscViewers(vv MetaViewers) {
vv[client.NewGVR("pulses")] = MetaViewer{ vv[client.NewGVR("pulses")] = MetaViewer{
viewerFn: NewPulse, viewerFn: NewPulse,
} }
vv[client.NewGVR("sanitizer")] = MetaViewer{
viewerFn: NewSanitizer,
}
} }
func appsViewers(vv MetaViewers) { func appsViewers(vv MetaViewers) {

View File

@ -1,436 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package view
import (
"context"
"fmt"
"log/slog"
"strings"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/xray"
"github.com/derailed/tcell/v2"
"github.com/derailed/tview"
"golang.org/x/text/cases"
"golang.org/x/text/language"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var _ ResourceViewer = (*Sanitizer)(nil)
// Sanitizer represents a sanitizer tree view.
type Sanitizer struct {
*ui.Tree
app *App
gvr client.GVR
meta metav1.APIResource
model *model.Tree
cancelFn context.CancelFunc
envFn EnvFunc
contextFn ContextFunc
}
// NewSanitizer returns a new view.
func NewSanitizer(gvr client.GVR) ResourceViewer {
return &Sanitizer{
gvr: gvr,
Tree: ui.NewTree(),
model: model.NewTree(gvr),
}
}
func (s *Sanitizer) SetFilter(string) {}
func (s *Sanitizer) SetLabelFilter(map[string]string) {}
// Init initializes the view.
func (s *Sanitizer) Init(ctx context.Context) error {
s.envFn = s.k9sEnv
if err := s.Tree.Init(ctx); err != nil {
return err
}
s.SetKeyListenerFn(s.keyEntered)
var err error
s.meta, err = dao.MetaAccess.MetaFor(s.gvr)
if err != nil {
return err
}
if s.app, err = extractApp(ctx); err != nil {
return err
}
s.bindKeys()
s.SetBackgroundColor(s.app.Styles.Xray().BgColor.Color())
s.SetBorderColor(s.app.Styles.Frame().Border.FgColor.Color())
s.SetBorderFocusColor(s.app.Styles.Frame().Border.FocusColor.Color())
s.SetGraphicsColor(s.app.Styles.Xray().GraphicColor.Color())
s.SetTitle(cases.Title(language.Und, cases.NoLower).String(s.gvr.R()))
s.model.SetNamespace(client.CleanseNamespace(s.app.Config.ActiveNamespace()))
s.model.AddListener(s)
s.SetChangedFunc(func(n *tview.TreeNode) {
spec, ok := n.GetReference().(xray.NodeSpec)
if !ok {
slog.Error("No ref field found on node", slogs.FQN, n.GetText())
return
}
s.SetSelectedItem(spec.AsPath())
s.refreshActions()
})
s.refreshActions()
return nil
}
// InCmdMode checks if prompt is active.
func (*Sanitizer) InCmdMode() bool {
return false
}
// ExtraHints returns additional hints.
func (s *Sanitizer) ExtraHints() map[string]string {
if s.app.Config.K9s.UI.NoIcons {
return nil
}
return xray.EmojiInfo()
}
// SetInstance sets specific resource instance.
func (s *Sanitizer) SetInstance(string) {}
func (s *Sanitizer) bindKeys() {
s.Actions().Bulk(ui.KeyMap{
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", s.activateCmd, false),
tcell.KeyEscape: ui.NewSharedKeyAction("Filter Reset", s.resetCmd, false),
tcell.KeyEnter: ui.NewKeyAction("Goto", s.gotoCmd, true),
})
}
func (s *Sanitizer) keyEntered() {
s.ClearSelection()
s.update(s.filter(s.model.Peek()))
}
func (s *Sanitizer) refreshActions() {}
// GetSelectedPath returns the current selection as string.
func (s *Sanitizer) GetSelectedPath() string {
spec := s.selectedSpec()
if spec == nil {
return ""
}
return spec.Path()
}
func (s *Sanitizer) selectedSpec() *xray.NodeSpec {
node := s.GetCurrentNode()
if node == nil {
return nil
}
ref, ok := node.GetReference().(xray.NodeSpec)
if !ok {
slog.Error("Expecting a NodeSpec", slogs.RefType, fmt.Sprintf("%T", node.GetReference()))
return nil
}
return &ref
}
// EnvFn returns an plugin env function if available.
func (s *Sanitizer) EnvFn() EnvFunc {
return s.envFn
}
func (s *Sanitizer) k9sEnv() Env {
env := k8sEnv(s.app.Conn().Config())
spec := s.selectedSpec()
if spec == nil {
return env
}
env["FILTER"] = s.CmdBuff().GetText()
if env["FILTER"] == "" {
ns, n := client.Namespaced(spec.Path())
env["NAMESPACE"], env["FILTER"] = ns, n
}
switch spec.GVR() {
case "containers":
_, co := client.Namespaced(spec.Path())
env["CONTAINER"] = co
ns, n := client.Namespaced(*spec.ParentPath())
env["NAMESPACE"], env["POD"], env["NAME"] = ns, n, co
default:
ns, n := client.Namespaced(spec.Path())
env["NAMESPACE"], env["NAME"] = ns, n
}
return env
}
// Aliases returns all available aliases.
func (s *Sanitizer) Aliases() []string {
return append(s.meta.ShortNames, s.meta.SingularName, s.meta.Name)
}
func (s *Sanitizer) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
if s.app.InCmdMode() {
return evt
}
s.app.ResetPrompt(s.CmdBuff())
return nil
}
func (s *Sanitizer) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
if !s.CmdBuff().InCmdMode() {
s.CmdBuff().Reset()
return s.app.PrevCmd(evt)
}
s.CmdBuff().Reset()
s.model.ClearFilter()
s.Start()
return nil
}
func (s *Sanitizer) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if s.CmdBuff().IsActive() {
if internal.IsLabelSelector(s.CmdBuff().GetText()) {
s.Start()
}
s.CmdBuff().SetActive(false)
s.GetRoot().ExpandAll()
return nil
}
spec := s.selectedSpec()
if spec == nil {
return nil
}
if len(spec.GVRs) <= 2 {
return nil
}
path := strings.Replace(spec.Path(), "::", "/", 1)
if strings.Contains(path, "[") {
return nil
}
if len(strings.Split(path, "/")) == 1 && spec.GVR() != "node" {
path = "-/" + path
}
s.app.gotoResource(client.NewGVR(spec.GVR()).R(), path, false, true)
return nil
}
func (s *Sanitizer) filter(root *xray.TreeNode) *xray.TreeNode {
q := s.CmdBuff().GetText()
if s.CmdBuff().Empty() || internal.IsLabelSelector(q) {
return root
}
s.UpdateTitle()
if f, ok := internal.IsFuzzySelector(q); ok {
return root.Filter(f, fuzzyFilter)
}
if internal.IsInverseSelector(q) {
return root.Filter(q, rxInverseFilter)
}
return root.Filter(q, rxFilter)
}
// TreeNodeSelected callback for node selection.
func (s *Sanitizer) TreeNodeSelected() {
s.app.QueueUpdateDraw(func() {
n := s.GetCurrentNode()
if n != nil {
n.SetColor(s.app.Styles.Xray().CursorColor.Color())
}
})
}
// TreeLoadFailed notifies the load failed.
func (s *Sanitizer) TreeLoadFailed(err error) {
s.app.Flash().Err(err)
}
func (s *Sanitizer) update(node *xray.TreeNode) {
root := makeTreeNode(node, s.ExpandNodes(), s.app.Config.K9s.UI.NoIcons, s.app.Styles)
if node == nil {
s.app.QueueUpdateDraw(func() {
s.SetRoot(root)
})
return
}
for _, c := range node.Children {
s.hydrate(root, c)
}
if s.GetSelectedItem() == "" {
s.SetSelectedItem(node.Spec().Path())
}
s.app.QueueUpdateDraw(func() {
s.SetRoot(root)
root.Walk(func(node, parent *tview.TreeNode) bool {
spec, ok := node.GetReference().(xray.NodeSpec)
if !ok {
slog.Error("Expecting a NodeSpec", slogs.RefType, fmt.Sprintf("%T", node.GetReference()))
return false
}
// BOZO!! Figure this out expand/collapse but the root
if parent != nil {
node.SetExpanded(s.ExpandNodes())
} else {
node.SetExpanded(true)
}
if spec.AsPath() == s.GetSelectedItem() {
node.SetExpanded(true).SetSelectable(true)
s.SetCurrentNode(node)
}
return true
})
})
}
// TreeChanged notifies the model data changed.
func (s *Sanitizer) TreeChanged(node *xray.TreeNode) {
s.Count = node.Count(s.gvr.String())
s.update(s.filter(node))
s.UpdateTitle()
}
func (s *Sanitizer) hydrate(parent *tview.TreeNode, n *xray.TreeNode) {
node := makeTreeNode(n, s.ExpandNodes(), s.app.Config.K9s.UI.NoIcons, s.app.Styles)
for _, c := range n.Children {
s.hydrate(node, c)
}
parent.AddChild(node)
}
// SetEnvFn sets the custom environment function.
func (s *Sanitizer) SetEnvFn(EnvFunc) {}
// Refresh updates the view.
func (s *Sanitizer) Refresh() {}
// BufferChanged indicates the buffer was changed.
func (s *Sanitizer) BufferChanged(_, _ string) {}
// BufferCompleted indicates input was accepted.
func (s *Sanitizer) BufferCompleted(_, _ string) {
s.update(s.filter(s.model.Peek()))
}
// BufferActive indicates the buff activity changed.
func (s *Sanitizer) BufferActive(state bool, k model.BufferKind) {
s.app.BufferActive(state, k)
}
func (s *Sanitizer) defaultContext() context.Context {
ctx := context.WithValue(context.Background(), internal.KeyFactory, s.app.factory)
ctx = context.WithValue(ctx, internal.KeyFields, "")
if s.CmdBuff().Empty() {
ctx = context.WithValue(ctx, internal.KeyLabels, "")
} else {
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(s.CmdBuff().GetText()))
}
return ctx
}
// Start initializes resource watch loop.
func (s *Sanitizer) Start() {
s.Stop()
s.CmdBuff().AddListener(s)
ctx := s.defaultContext()
ctx, s.cancelFn = context.WithCancel(ctx)
if s.contextFn != nil {
ctx = s.contextFn(ctx)
}
s.model.Refresh(ctx)
s.UpdateTitle()
}
// Stop terminates watch loop.
func (s *Sanitizer) Stop() {
if s.cancelFn == nil {
return
}
s.cancelFn()
s.cancelFn = nil
s.CmdBuff().RemoveListener(s)
}
// AddBindKeysFn sets up extra key bindings.
func (s *Sanitizer) AddBindKeysFn(BindKeysFunc) {}
// SetContextFn sets custom context.
func (s *Sanitizer) SetContextFn(f ContextFunc) {
s.contextFn = f
}
// Name returns the component name.
func (s *Sanitizer) Name() string { return "report" }
// GetTable returns the underlying table.
func (s *Sanitizer) GetTable() *Table { return nil }
// GVR returns a resource descriptor.
func (s *Sanitizer) GVR() client.GVR { return s.gvr }
// App returns the current app handle.
func (s *Sanitizer) App() *App {
return s.app
}
// UpdateTitle updates the view title.
func (s *Sanitizer) UpdateTitle() {
t := s.styleTitle()
s.app.QueueUpdateDraw(func() {
s.SetTitle(t)
})
}
func (s *Sanitizer) styleTitle() string {
base := cases.Title(language.Und, cases.NoLower).String(s.gvr.R())
ns := s.model.GetNamespace()
if client.IsAllNamespaces(ns) {
ns = client.NamespaceAll
}
var title string
if ns == client.ClusterScope {
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, ui.ROIndicator(s.app.Config.IsReadOnly()), base, render.AsThousands(int64(s.Count))), s.app.Styles.Frame())
} else {
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, ui.ROIndicator(s.app.Config.IsReadOnly()), base, ns, render.AsThousands(int64(s.Count))), s.app.Styles.Frame())
}
buff := s.CmdBuff().GetText()
if buff == "" {
return title
}
if internal.IsLabelSelector(buff) {
buff = ui.TrimLabelSelector(buff)
}
return title + ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, buff), s.app.Styles.Frame())
}

View File

@ -5,12 +5,12 @@ package view
import ( import (
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config/data"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/yaml"
) )
// Secret presents a secret viewer. // Secret presents a secret viewer.
@ -57,7 +57,7 @@ func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil return nil
} }
raw, err := yaml.Marshal(d) raw, err := data.WriteYAML(d)
if err != nil { if err != nil {
s.App().Flash().Errf("Error decoding secret %s", err) s.App().Flash().Errf("Error decoding secret %s", err)
return nil return nil

View File

@ -12,11 +12,11 @@ import (
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
) )
@ -28,6 +28,7 @@ type Table struct {
enterFn EnterFunc enterFn EnterFunc
envFn EnvFunc envFn EnvFunc
bindKeysFn []BindKeysFunc bindKeysFn []BindKeysFunc
command *cmd.Interpreter
} }
// NewTable returns a new viewer. // NewTable returns a new viewer.
@ -48,11 +49,8 @@ func (t *Table) Init(ctx context.Context) (err error) {
if t.app.Conn() != nil { if t.app.Conn() != nil {
ctx = context.WithValue(ctx, internal.KeyHasMetrics, t.app.Conn().HasMetrics()) ctx = context.WithValue(ctx, internal.KeyHasMetrics, t.app.Conn().HasMetrics())
} }
if t.app.CustomView == nil {
t.app.CustomView = config.NewCustomView()
}
ctx = context.WithValue(ctx, internal.KeyStyles, t.app.Styles) ctx = context.WithValue(ctx, internal.KeyStyles, t.app.Styles)
ctx = context.WithValue(ctx, internal.KeyViewConfig, t.app.CustomView) ctx = context.WithValue(ctx, internal.KeyViewConfig, t.app.CustomView())
t.Table.Init(ctx) t.Table.Init(ctx)
if !t.app.Config.K9s.UI.Reactive { if !t.app.Config.K9s.UI.Reactive {
if err := t.app.RefreshCustomViews(); err != nil { if err := t.app.RefreshCustomViews(); err != nil {
@ -68,6 +66,11 @@ func (t *Table) Init(ctx context.Context) (err error) {
return nil return nil
} }
// SetCommand sets the current command.
func (t *Table) SetCommand(cmd *cmd.Interpreter) {
t.command = cmd
}
// HeaderIndex returns index of a given column or false if not found. // HeaderIndex returns index of a given column or false if not found.
func (t *Table) HeaderIndex(colName string) (int, bool) { func (t *Table) HeaderIndex(colName string) (int, bool) {
for i := 0; i < t.GetColumnCount(); i++ { for i := 0; i < t.GetColumnCount(); i++ {
@ -145,14 +148,18 @@ func (t *Table) Start() {
t.Stop() t.Stop()
t.CmdBuff().AddListener(t) t.CmdBuff().AddListener(t)
t.Styles().AddListener(t.Table) t.Styles().AddListener(t.Table)
t.App().CustomView.AddListener(t.Table.GVR().String(), t.Table) cmds := []string{t.Table.GVR().String()}
if t.command != nil {
cmds = append(cmds, t.command.GetLine())
}
t.App().CustomView().AddListeners(t.Table, cmds...)
} }
// Stop terminates the component. // Stop terminates the component.
func (t *Table) Stop() { func (t *Table) Stop() {
t.CmdBuff().RemoveListener(t) t.CmdBuff().RemoveListener(t)
t.Styles().RemoveListener(t.Table) t.Styles().RemoveListener(t.Table)
t.App().CustomView.RemoveListener(t.GVR().String()) t.App().CustomView().RemoveListener(t.Table)
} }
// SetEnterFn specifies the default enter behavior. // SetEnterFn specifies the default enter behavior.

View File

@ -10,6 +10,7 @@ import (
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/view/cmd"
) )
const ( const (
@ -95,6 +96,9 @@ type ResourceViewer interface {
// SetInstance sets a parent FQN // SetInstance sets a parent FQN
SetInstance(string) SetInstance(string)
// SetCommand sets the current command.
SetCommand(*cmd.Interpreter)
} }
// LogViewer represents a log viewer. // LogViewer represents a log viewer.

View File

@ -20,6 +20,7 @@ import (
"github.com/derailed/k9s/internal/slogs" "github.com/derailed/k9s/internal/slogs"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/k9s/internal/ui/dialog"
"github.com/derailed/k9s/internal/view/cmd"
"github.com/derailed/k9s/internal/xray" "github.com/derailed/k9s/internal/xray"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
"github.com/derailed/tview" "github.com/derailed/tview"
@ -54,6 +55,7 @@ func NewXray(gvr client.GVR) ResourceViewer {
} }
} }
func (x *Xray) SetCommand(*cmd.Interpreter) {}
func (x *Xray) SetFilter(string) {} func (x *Xray) SetFilter(string) {}
func (x *Xray) SetLabelFilter(map[string]string) {} func (x *Xray) SetLabelFilter(map[string]string) {}
@ -671,9 +673,12 @@ func (x *Xray) styleTitle() string {
var title string var title string
if ns == client.ClusterScope { if ns == client.ClusterScope {
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, ui.ROIndicator(x.app.Config.IsReadOnly()), base, render.AsThousands(int64(x.Count))), x.app.Styles.Frame()) title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, render.AsThousands(int64(x.Count))), x.app.Styles.Frame())
} else { } else {
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, ui.ROIndicator(x.app.Config.IsReadOnly()), base, ns, render.AsThousands(int64(x.Count))), x.app.Styles.Frame()) title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, render.AsThousands(int64(x.Count))), x.app.Styles.Frame())
}
if ic := ui.ROIndicator(x.app.Config.IsReadOnly(), x.app.Config.K9s.UI.NoIcons); ic != "" {
title = " " + ic + title
} }
buff := x.CmdBuff().GetText() buff := x.CmdBuff().GetText()

View File

@ -9,7 +9,6 @@ import (
"strings" "strings"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/derailed/popeye/pkg/config"
) )
// Section represents an xray renderer. // Section represents an xray renderer.
@ -59,15 +58,15 @@ func (*Section) outcomeRefs(parent *TreeNode, section render.Section) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Helpers... // Helpers...
func colorize(s string, l config.Level) string { func colorize(s string, l render.Level) string {
c := "green" c := "green"
// nolint:exhaustive // nolint:exhaustive
switch l { switch l {
case config.ErrorLevel: case render.ErrorLevel:
c = "red" c = "red"
case config.WarnLevel: case render.WarnLevel:
c = "orange" c = "orange"
case config.InfoLevel: case render.InfoLevel:
c = "blue" c = "blue"
} }
return fmt.Sprintf("[%s::]%s", c, s) return fmt.Sprintf("[%s::]%s", c, s)