bug fixes + cleanup
parent
8778305bf2
commit
ffd1c61c8c
|
|
@ -8,7 +8,7 @@ import (
|
|||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// K9sAlias stores K9s command aliases.
|
||||
// K9sAlias manages K9s aliases.
|
||||
var K9sAlias = filepath.Join(K9sHome, "alias.yml")
|
||||
|
||||
type Alias map[string]string
|
||||
|
|
@ -33,7 +33,7 @@ func (a Aliases) loadDefaults() {
|
|||
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["np"] = "networking.k8s.io/v1/networkpolicies"
|
||||
{
|
||||
a.Alias["ctx"] = "contexts"
|
||||
a.Alias["contexts"] = "contexts"
|
||||
|
|
@ -87,7 +87,7 @@ func (a Aliases) Define(args ...string) {
|
|||
}
|
||||
}
|
||||
|
||||
// LoadAliases K9s alias from a given file.
|
||||
// LoadAliases loads alias from a given file.
|
||||
func (a Aliases) LoadAliases(path string) error {
|
||||
f, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -7,20 +7,42 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAliasesLoad(t *testing.T) {
|
||||
aa := config.NewAliases()
|
||||
assert.Nil(t, aa.LoadAliases("test_assets/alias.yml"))
|
||||
func TestAliasDefine(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
aa []string
|
||||
}{
|
||||
"one": {[]string{"blee", "duh"}},
|
||||
"multi": {[]string{"blee", "duh", "fred", "zorg"}},
|
||||
}
|
||||
|
||||
assert.Equal(t, 27, len(aa.Alias))
|
||||
for k, u := range uu {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
a := config.NewAliases()
|
||||
a.Define(u.aa...)
|
||||
for i := 0; i < len(u.aa); i += 2 {
|
||||
v, ok := a.Get(u.aa[i])
|
||||
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, u.aa[i+1], v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAliasesLoad(t *testing.T) {
|
||||
a := config.NewAliases()
|
||||
assert.Nil(t, a.LoadAliases("test_assets/alias.yml"))
|
||||
|
||||
assert.Equal(t, 27, len(a.Alias))
|
||||
}
|
||||
|
||||
func TestAliasesSave(t *testing.T) {
|
||||
aa := config.NewAliases()
|
||||
a := config.NewAliases()
|
||||
|
||||
aa.Alias["test"] = "fred"
|
||||
aa.Alias["blee"] = "duh"
|
||||
aa.SaveAliases("/tmp/a.yml")
|
||||
a.Alias["test"] = "fred"
|
||||
a.Alias["blee"] = "duh"
|
||||
a.SaveAliases("/tmp/a.yml")
|
||||
|
||||
assert.Nil(t, aa.LoadAliases("/tmp/a.yml"))
|
||||
assert.Equal(t, 28, len(aa.Alias))
|
||||
assert.Nil(t, a.LoadAliases("/tmp/a.yml"))
|
||||
assert.Equal(t, 28, len(a.Alias))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,6 @@ func TestConfigLoad(t *testing.T) {
|
|||
assert.Equal(t, "minikube", cfg.K9s.CurrentCluster)
|
||||
assert.NotNil(t, cfg.K9s.Clusters)
|
||||
assert.Equal(t, 2, len(cfg.K9s.Clusters))
|
||||
assert.Equal(t, 1, len(cfg.K9s.Plugins))
|
||||
|
||||
nn := []string{
|
||||
"default",
|
||||
|
|
@ -294,19 +293,6 @@ var expectedConfig = `k9s:
|
|||
- kube-system
|
||||
view:
|
||||
active: ctx
|
||||
plugins:
|
||||
blah:
|
||||
shortCut: shift-s
|
||||
scopes:
|
||||
- po
|
||||
- dp
|
||||
description: blee
|
||||
command: duh
|
||||
background: false
|
||||
args:
|
||||
- -n
|
||||
- $NAMESPACE
|
||||
- -boolean
|
||||
`
|
||||
|
||||
var resetConfig = `k9s:
|
||||
|
|
@ -324,17 +310,4 @@ var resetConfig = `k9s:
|
|||
- default
|
||||
view:
|
||||
active: po
|
||||
plugins:
|
||||
blah:
|
||||
shortCut: shift-s
|
||||
scopes:
|
||||
- po
|
||||
- dp
|
||||
description: blee
|
||||
command: duh
|
||||
background: false
|
||||
args:
|
||||
- -n
|
||||
- $NAMESPACE
|
||||
- -boolean
|
||||
`
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ type K9s struct {
|
|||
CurrentContext string `yaml:"currentContext"`
|
||||
CurrentCluster string `yaml:"currentCluster"`
|
||||
Clusters map[string]*Cluster `yaml:"clusters,omitempty"`
|
||||
Plugins map[string]*Plugin `yaml:"plugins,omitempty"`
|
||||
manualRefreshRate int
|
||||
manualHeadless *bool
|
||||
manualCommand *string
|
||||
|
|
@ -32,7 +31,6 @@ func NewK9s() *K9s {
|
|||
LogBufferSize: defaultLogBufferSize,
|
||||
LogRequestSize: defaultLogRequestSize,
|
||||
Clusters: make(map[string]*Cluster),
|
||||
Plugins: make(map[string]*Plugin),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,20 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// K9sPlugins manages K9s plugins.
|
||||
var K9sPlugins = filepath.Join(K9sHome, "plugin.yml")
|
||||
|
||||
// Plugins represents a collection of plugins.
|
||||
type Plugins struct {
|
||||
Plugin map[string]Plugin `yaml:"plugin"`
|
||||
}
|
||||
|
||||
// Plugin describes a K9s plugin
|
||||
type Plugin struct {
|
||||
ShortCut string `yaml:"shortCut"`
|
||||
|
|
@ -9,3 +24,33 @@ type Plugin struct {
|
|||
Background bool `yaml:"background"`
|
||||
Args []string `yaml:"args"`
|
||||
}
|
||||
|
||||
// NewPlugins returns a new plugin.
|
||||
func NewPlugins() Plugins {
|
||||
return Plugins{
|
||||
Plugin: make(map[string]Plugin),
|
||||
}
|
||||
}
|
||||
|
||||
// Load K9s plugins.
|
||||
func (p Plugins) Load() error {
|
||||
return p.LoadPlugins(K9sPlugins)
|
||||
}
|
||||
|
||||
// LoadPlugins loads plugins from a given file.
|
||||
func (p Plugins) LoadPlugins(path string) error {
|
||||
f, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pp Plugins
|
||||
if err := yaml.Unmarshal(f, &pp); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range pp.Plugin {
|
||||
p.Plugin[k] = v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
package config_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPluginLoad(t *testing.T) {
|
||||
p := config.NewPlugins()
|
||||
assert.Nil(t, p.LoadPlugins("test_assets/plugin.yml"))
|
||||
|
||||
assert.Equal(t, 1, len(p.Plugin))
|
||||
}
|
||||
|
|
@ -197,7 +197,7 @@ func newTitle() Title {
|
|||
BgColor: "black",
|
||||
HighlightColor: "fuchsia",
|
||||
CounterColor: "papayawhip",
|
||||
FilterColor: "orange",
|
||||
FilterColor: "seagreen",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,15 +27,3 @@ k9s:
|
|||
- kube-system
|
||||
view:
|
||||
active: po
|
||||
plugins:
|
||||
blah:
|
||||
shortCut: shift-s
|
||||
description: blee
|
||||
scopes:
|
||||
- po
|
||||
- dp
|
||||
command: duh
|
||||
args:
|
||||
- -n
|
||||
- $NAMESPACE
|
||||
- -boolean
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
plugin:
|
||||
blah:
|
||||
shortCut: shift-s
|
||||
description: blee
|
||||
scopes:
|
||||
- po
|
||||
- dp
|
||||
command: duh
|
||||
args:
|
||||
- -n
|
||||
- $NAMESPACE
|
||||
- -boolean
|
||||
|
|
@ -16,7 +16,6 @@ import (
|
|||
|
||||
var (
|
||||
// RestMapping holds k8s resource mapping
|
||||
// BOZO!! Has to be a better way...
|
||||
RestMapping = &RestMapper{}
|
||||
toFileName = regexp.MustCompile(`[^(\w/\.)]`)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ func NewApp() *App {
|
|||
actions: make(KeyActions),
|
||||
pages: tview.NewPages(),
|
||||
content: tview.NewPages(),
|
||||
cmdBuff: NewCmdBuff(':'),
|
||||
cmdBuff: NewCmdBuff(':', CommandBuff),
|
||||
}
|
||||
|
||||
s.RefreshStyles()
|
||||
|
|
@ -62,7 +62,7 @@ func NewApp() *App {
|
|||
s.views = map[string]tview.Primitive{
|
||||
"menu": NewMenuView(s.Styles),
|
||||
"logo": NewLogoView(s.Styles),
|
||||
"cmd": NewCmdView(s.Styles, '🐶'),
|
||||
"cmd": NewCmdView(s.Styles),
|
||||
"crumbs": NewCrumbsView(s.Styles),
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +186,6 @@ func (a *App) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
if a.InCmdMode() {
|
||||
return evt
|
||||
}
|
||||
a.Flash().Info("Command mode activated.")
|
||||
a.cmdBuff.SetActive(true)
|
||||
a.cmdBuff.Clear()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
const defaultPrompt = "%c> %s"
|
||||
|
|
@ -20,8 +21,8 @@ type CmdView struct {
|
|||
}
|
||||
|
||||
// NewCmdView returns a new command view.
|
||||
func NewCmdView(styles *config.Styles, ic rune) *CmdView {
|
||||
v := CmdView{styles: styles, icon: ic, TextView: tview.NewTextView()}
|
||||
func NewCmdView(styles *config.Styles) *CmdView {
|
||||
v := CmdView{styles: styles, TextView: tview.NewTextView()}
|
||||
{
|
||||
v.SetWordWrap(true)
|
||||
v.SetWrap(true)
|
||||
|
|
@ -29,7 +30,7 @@ func NewCmdView(styles *config.Styles, ic rune) *CmdView {
|
|||
v.SetBorder(true)
|
||||
v.SetBorderPadding(0, 0, 1, 1)
|
||||
v.SetBackgroundColor(styles.BgColor())
|
||||
v.SetBorderColor(config.AsColor(styles.Frame().Border.FocusColor))
|
||||
// v.SetBorderColor(config.AsColor(styles.Frame().Border.FocusColor))
|
||||
v.SetTextColor(styles.FgColor())
|
||||
}
|
||||
return &v
|
||||
|
|
@ -67,11 +68,13 @@ func (v *CmdView) BufferChanged(s string) {
|
|||
}
|
||||
|
||||
// BufferActive indicates the buff activity changed.
|
||||
func (v *CmdView) BufferActive(f bool) {
|
||||
func (v *CmdView) BufferActive(f bool, k BufferKind) {
|
||||
v.activated = f
|
||||
if f {
|
||||
v.SetBorder(true)
|
||||
v.icon = iconFor(k)
|
||||
v.SetTextColor(v.styles.FgColor())
|
||||
v.SetBorderColor(colorFor(k))
|
||||
v.activate()
|
||||
} else {
|
||||
v.SetBorder(false)
|
||||
|
|
@ -79,3 +82,20 @@ func (v *CmdView) BufferActive(f bool) {
|
|||
v.Clear()
|
||||
}
|
||||
}
|
||||
|
||||
func colorFor(k BufferKind) tcell.Color {
|
||||
switch k {
|
||||
case CommandBuff:
|
||||
return tcell.ColorAqua
|
||||
default:
|
||||
return tcell.ColorSeaGreen
|
||||
}
|
||||
}
|
||||
func iconFor(k BufferKind) rune {
|
||||
switch k {
|
||||
case CommandBuff:
|
||||
return '🐶'
|
||||
default:
|
||||
return '🤓'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,30 @@ package ui
|
|||
|
||||
const maxBuff = 10
|
||||
|
||||
const (
|
||||
// CommandBuff indicates a command buffer.
|
||||
CommandBuff BufferKind = 1 << iota
|
||||
// FilterBuff indicates a search buffer.
|
||||
FilterBuff
|
||||
)
|
||||
|
||||
type (
|
||||
// BufferKind indicates a buffer type
|
||||
BufferKind int8
|
||||
|
||||
// BuffWatcher represents a command buffer listener.
|
||||
BuffWatcher interface {
|
||||
// Changed indicates the buffer was changed.
|
||||
BufferChanged(s string)
|
||||
|
||||
// Active indicates the buff activity changed.
|
||||
BufferActive(state bool)
|
||||
BufferActive(state bool, kind BufferKind)
|
||||
}
|
||||
|
||||
// CmdBuff represents user command input.
|
||||
CmdBuff struct {
|
||||
buff []rune
|
||||
kind BufferKind
|
||||
hotKey rune
|
||||
active bool
|
||||
listeners []BuffWatcher
|
||||
|
|
@ -22,9 +33,10 @@ type (
|
|||
)
|
||||
|
||||
// NewCmdBuff returns a new command buffer.
|
||||
func NewCmdBuff(key rune) *CmdBuff {
|
||||
func NewCmdBuff(key rune, kind BufferKind) *CmdBuff {
|
||||
return &CmdBuff{
|
||||
hotKey: key,
|
||||
kind: kind,
|
||||
buff: make([]rune, 0, maxBuff),
|
||||
listeners: []BuffWatcher{},
|
||||
}
|
||||
|
|
@ -47,8 +59,9 @@ func (c *CmdBuff) String() string {
|
|||
}
|
||||
|
||||
// Set initializes the buffer with a command.
|
||||
func (c *CmdBuff) Set(rr []rune) {
|
||||
c.buff = rr
|
||||
func (c *CmdBuff) Set(cmd string) {
|
||||
c.buff = []rune(cmd)
|
||||
c.fireChanged()
|
||||
}
|
||||
|
||||
// Add adds a new charater to the buffer.
|
||||
|
|
@ -104,6 +117,6 @@ func (c *CmdBuff) fireChanged() {
|
|||
|
||||
func (c *CmdBuff) fireActive(b bool) {
|
||||
for _, l := range c.listeners {
|
||||
l.BufferActive(b)
|
||||
l.BufferActive(b, c.kind)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ func (l *testListener) BufferChanged(s string) {
|
|||
l.text = s
|
||||
}
|
||||
|
||||
func (l *testListener) BufferActive(s bool) {
|
||||
func (l *testListener) BufferActive(s bool, _ BufferKind) {
|
||||
if s {
|
||||
l.act++
|
||||
return
|
||||
|
|
@ -25,7 +25,7 @@ func (l *testListener) BufferActive(s bool) {
|
|||
}
|
||||
|
||||
func TestCmdBuffActivate(t *testing.T) {
|
||||
b, l := NewCmdBuff('>'), testListener{}
|
||||
b, l := NewCmdBuff('>', CommandBuff), testListener{}
|
||||
b.AddListener(&l)
|
||||
|
||||
b.SetActive(true)
|
||||
|
|
@ -35,7 +35,7 @@ func TestCmdBuffActivate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCmdBuffDeactivate(t *testing.T) {
|
||||
b, l := NewCmdBuff('>'), testListener{}
|
||||
b, l := NewCmdBuff('>', CommandBuff), testListener{}
|
||||
b.AddListener(&l)
|
||||
|
||||
b.SetActive(false)
|
||||
|
|
@ -45,7 +45,7 @@ func TestCmdBuffDeactivate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCmdBuffChanged(t *testing.T) {
|
||||
b, l := NewCmdBuff('>'), testListener{}
|
||||
b, l := NewCmdBuff('>', CommandBuff), testListener{}
|
||||
b.AddListener(&l)
|
||||
|
||||
b.Add('b')
|
||||
|
|
@ -77,7 +77,7 @@ func TestCmdBuffChanged(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCmdBuffAdd(t *testing.T) {
|
||||
b := NewCmdBuff('>')
|
||||
b := NewCmdBuff('>', CommandBuff)
|
||||
|
||||
uu := []struct {
|
||||
runes []rune
|
||||
|
|
@ -98,7 +98,7 @@ func TestCmdBuffAdd(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCmdBuffDel(t *testing.T) {
|
||||
b := NewCmdBuff('>')
|
||||
b := NewCmdBuff('>', CommandBuff)
|
||||
|
||||
uu := []struct {
|
||||
runes []rune
|
||||
|
|
@ -120,7 +120,7 @@ func TestCmdBuffDel(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCmdBuffEmpty(t *testing.T) {
|
||||
b := NewCmdBuff('>')
|
||||
b := NewCmdBuff('>', CommandBuff)
|
||||
|
||||
uu := []struct {
|
||||
runes []rune
|
||||
|
|
|
|||
|
|
@ -9,20 +9,20 @@ import (
|
|||
|
||||
func TestNewCmdUpdate(t *testing.T) {
|
||||
defaults, _ := config.NewStyles("")
|
||||
v := NewCmdView(defaults, 'T')
|
||||
v := NewCmdView(defaults)
|
||||
v.update("blee")
|
||||
|
||||
assert.Equal(t, "T> blee\n", v.GetText(false))
|
||||
assert.Equal(t, "\x00> blee\n", v.GetText(false))
|
||||
}
|
||||
|
||||
func TestCmdInCmdMode(t *testing.T) {
|
||||
defaults, _ := config.NewStyles("")
|
||||
v := NewCmdView(defaults, 'T')
|
||||
v := NewCmdView(defaults)
|
||||
v.update("blee")
|
||||
v.append('!')
|
||||
|
||||
assert.Equal(t, "T> blee!\n", v.GetText(false))
|
||||
assert.Equal(t, "\x00> blee!\n", v.GetText(false))
|
||||
assert.False(t, v.InCmdMode())
|
||||
v.BufferActive(true)
|
||||
v.BufferActive(true, CommandBuff)
|
||||
assert.True(t, v.InCmdMode())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ func (v *MenuView) HydrateMenu(hh Hints) {
|
|||
t := v.buildMenuTable(hh)
|
||||
for row := 0; row < len(t); row++ {
|
||||
for col := 0; col < len(t[row]); col++ {
|
||||
if len(t[row][col]) == 0 {
|
||||
if t[row][col] == "" {
|
||||
continue
|
||||
}
|
||||
c := tview.NewTableCell(t[row][col])
|
||||
|
|
@ -57,33 +57,42 @@ func (v *MenuView) HydrateMenu(hh Hints) {
|
|||
}
|
||||
}
|
||||
|
||||
func (v *MenuView) buildMenuTable(hh Hints) [][]string {
|
||||
table := make([][]Hint, maxRows+1)
|
||||
|
||||
colCount := (len(hh) / maxRows) + 1
|
||||
for row := 0; row < maxRows; row++ {
|
||||
table[row] = make([]Hint, colCount+1)
|
||||
func isDigit(s string) bool {
|
||||
return menuRX.MatchString(s)
|
||||
}
|
||||
|
||||
var row, col int
|
||||
func (v *MenuView) buildMenuTable(hh Hints) [][]string {
|
||||
table := make([][]Hint, maxRows)
|
||||
colCount := len(hh) / maxRows
|
||||
if colCount == 0 {
|
||||
colCount = 1
|
||||
}
|
||||
if isDigit(hh[0].Mnemonic) {
|
||||
colCount++
|
||||
}
|
||||
for row := 0; row < maxRows; row++ {
|
||||
table[row] = make([]Hint, colCount)
|
||||
}
|
||||
var row, col, added int
|
||||
firstCmd := true
|
||||
maxKeys := make([]int, colCount+1)
|
||||
for _, h := range hh {
|
||||
if !h.Visible {
|
||||
continue
|
||||
}
|
||||
isDigit := menuRX.MatchString(h.Mnemonic)
|
||||
if !isDigit && firstCmd {
|
||||
if !isDigit(h.Mnemonic) && firstCmd {
|
||||
row, col, firstCmd = 0, col+1, false
|
||||
if added == 0 {
|
||||
col = 0
|
||||
}
|
||||
}
|
||||
if maxKeys[col] < len(h.Mnemonic) {
|
||||
maxKeys[col] = len(h.Mnemonic)
|
||||
}
|
||||
table[row][col] = h
|
||||
row++
|
||||
added, row = added+1, row+1
|
||||
if row >= maxRows {
|
||||
col++
|
||||
row = 0
|
||||
row, col = 0, col+1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,6 +257,7 @@ func initKeys() {
|
|||
initNumbKeys()
|
||||
initStdKeys()
|
||||
initShiftKeys()
|
||||
initCtrlKeys()
|
||||
}
|
||||
|
||||
func initNumbKeys() {
|
||||
|
|
@ -292,6 +302,12 @@ func initStdKeys() {
|
|||
tcell.KeyNames[tcell.Key(KeyZ)] = "z"
|
||||
}
|
||||
|
||||
// BOZO!! No sure why these aren't mapped??
|
||||
func initCtrlKeys() {
|
||||
tcell.KeyNames[tcell.KeyCtrlI] = "Ctrl-I"
|
||||
tcell.KeyNames[tcell.KeyCtrlM] = "Ctrl-M"
|
||||
}
|
||||
|
||||
func initShiftKeys() {
|
||||
tcell.KeyNames[tcell.Key(KeyShiftA)] = "Shift-A"
|
||||
tcell.KeyNames[tcell.Key(KeyShiftB)] = "Shift-B"
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ func TestNewMenuView(t *testing.T) {
|
|||
defaults, _ := config.NewStyles("")
|
||||
v := NewMenuView(defaults)
|
||||
v.HydrateMenu(Hints{
|
||||
{"0", "zero", true},
|
||||
{"a", "bleeA", true},
|
||||
{"b", "bleeB", true},
|
||||
{"0", "zero", true},
|
||||
})
|
||||
|
||||
assert.Equal(t, " [fuchsia:black:b]<0> [white:black:d]zero ", v.GetCell(0, 0).Text)
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ import (
|
|||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
|
||||
// "github.com/ktr0731/go-fuzzyfinder/matching"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/sahilm/fuzzy"
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
|
|
@ -52,7 +50,7 @@ func NewTable(title string, styles *config.Styles) *Table {
|
|||
Table: tview.NewTable(),
|
||||
styles: styles,
|
||||
actions: make(KeyActions),
|
||||
cmdBuff: NewCmdBuff('/'),
|
||||
cmdBuff: NewCmdBuff('/', FilterBuff),
|
||||
baseTitle: title,
|
||||
sortCol: SortColumn{0, 0, true},
|
||||
}
|
||||
|
|
@ -178,6 +176,7 @@ func (v *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
|||
v.SearchBuff().Add(evt.Rune())
|
||||
v.ClearSelection()
|
||||
v.doUpdate(v.filtered())
|
||||
v.UpdateTitle()
|
||||
v.SelectFirstRow()
|
||||
return nil
|
||||
}
|
||||
|
|
@ -383,7 +382,7 @@ func (v *Table) filtered() resource.TableData {
|
|||
|
||||
q := v.cmdBuff.String()
|
||||
if isFuzzySelector(q) {
|
||||
return v.fuzzFilter(q[2:])
|
||||
return v.fuzzyFilter(q[2:])
|
||||
}
|
||||
|
||||
return v.rxFilter(q)
|
||||
|
|
@ -412,7 +411,7 @@ func (v *Table) rxFilter(q string) resource.TableData {
|
|||
return filtered
|
||||
}
|
||||
|
||||
func (v *Table) fuzzFilter(q string) resource.TableData {
|
||||
func (v *Table) fuzzyFilter(q string) resource.TableData {
|
||||
var ss, kk []string
|
||||
for k, row := range v.data.Rows {
|
||||
ss = append(ss, row.Fields[v.NameColIndex()])
|
||||
|
|
@ -506,7 +505,7 @@ func (v *Table) UpdateTitle() {
|
|||
title = skinTitle(fmt.Sprintf(nsTitleFmt, v.baseTitle, ns, rc), v.styles.Frame())
|
||||
}
|
||||
|
||||
if !v.cmdBuff.IsActive() && !v.cmdBuff.Empty() {
|
||||
if !v.cmdBuff.Empty() {
|
||||
cmd := v.cmdBuff.String()
|
||||
if isLabelSelector(cmd) {
|
||||
cmd = trimLabelSelector(cmd)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ func (v *aliasView) Init(context.Context, string) {
|
|||
|
||||
func (v *aliasView) registerActions() {
|
||||
v.RmAction(ui.KeyShiftA)
|
||||
v.RmAction(tcell.KeyCtrlS)
|
||||
|
||||
v.SetActions(ui.KeyActions{
|
||||
tcell.KeyEnter: ui.NewKeyAction("Goto", v.gotoCmd, true),
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ const (
|
|||
splashTime = 1
|
||||
devMode = "dev"
|
||||
clusterRefresh = time.Duration(5 * time.Second)
|
||||
indicatorFmt = "[orange::b]K9s [aqua::]%s [white::]%s:%s:%s [lawngreen::]%s%%[white::]::[darkturquoise::]%s%%"
|
||||
)
|
||||
|
||||
type (
|
||||
|
|
@ -55,6 +56,7 @@ type (
|
|||
forwarders map[string]forwarder
|
||||
version string
|
||||
showHeader bool
|
||||
filter string
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -105,7 +107,6 @@ func (a *appView) Init(version string, rate int) {
|
|||
a.Main().AddPage("splash", ui.NewSplash(a.Styles, version), true, true)
|
||||
|
||||
main.AddItem(a.indicator(), 1, 1, false)
|
||||
// main.AddItem(a.Cmd(), 3, 1, false)
|
||||
main.AddItem(a.Frame(), 0, 10, true)
|
||||
main.AddItem(a.Crumbs(), 2, 1, false)
|
||||
main.AddItem(a.Flash(), 1, 1, false)
|
||||
|
|
@ -116,7 +117,7 @@ func (a *appView) Init(version string, rate int) {
|
|||
func (a *appView) BufferChanged(s string) {}
|
||||
|
||||
// Active indicates the buff activity changed.
|
||||
func (a *appView) BufferActive(state bool) {
|
||||
func (a *appView) BufferActive(state bool, _ ui.BufferKind) {
|
||||
flex, ok := a.Main().GetPrimitive("main").(*tview.Flex)
|
||||
if !ok {
|
||||
return
|
||||
|
|
@ -144,6 +145,7 @@ func (a *appView) toggleHeader(flag bool) {
|
|||
|
||||
func (a *appView) buildHeader() tview.Primitive {
|
||||
header := tview.NewFlex()
|
||||
header.SetBorderPadding(0, 0, 1, 1)
|
||||
header.SetDirection(tview.FlexColumn)
|
||||
if !a.showHeader {
|
||||
return header
|
||||
|
|
@ -192,7 +194,7 @@ func (a *appView) refreshIndicator() {
|
|||
}
|
||||
|
||||
info := fmt.Sprintf(
|
||||
"[orange::b]K9s [aqua::]%s [white::]%s:%s:%s [lawngreen::]%s%%[white::]::[darkturquoise::]%s%%",
|
||||
indicatorFmt,
|
||||
a.version,
|
||||
cluster.ClusterName(),
|
||||
cluster.UserName(),
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ func (c *command) defaultCmd() {
|
|||
|
||||
// Helpers...
|
||||
|
||||
var policyMatcher = regexp.MustCompile(`\Apol\s([u|g|s]):([\w-:]+)\b`)
|
||||
var authRX = regexp.MustCompile(`\Apol\s([u|g|s]):([\w-:]+)\b`)
|
||||
|
||||
func (c *command) isK9sCmd(cmd string) bool {
|
||||
cmds := strings.Split(cmd, " ")
|
||||
|
|
@ -69,10 +69,10 @@ func (c *command) isK9sCmd(cmd string) bool {
|
|||
c.app.aliasCmd(nil)
|
||||
return true
|
||||
default:
|
||||
if !policyMatcher.MatchString(cmd) {
|
||||
if !authRX.MatchString(cmd) {
|
||||
return false
|
||||
}
|
||||
tokens := policyMatcher.FindAllStringSubmatch(cmd, -1)
|
||||
tokens := authRX.FindAllStringSubmatch(cmd, -1)
|
||||
if len(tokens) == 1 && len(tokens[0]) == 3 {
|
||||
c.app.inject(newPolicyView(c.app, tokens[0][1], tokens[0][2]))
|
||||
return true
|
||||
|
|
@ -100,8 +100,8 @@ func (c *command) viewMetaFor(cmd string) (string, *viewer) {
|
|||
}
|
||||
v, ok := vv[gvr]
|
||||
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)
|
||||
log.Error().Err(fmt.Errorf("Huh? `%s` viewer not found", gvr)).Msg("Viewer Failed")
|
||||
c.app.Flash().Warnf("Huh? viewer for %s not found", cmd)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ func (v *containerView) k9sEnv() K9sEnv {
|
|||
ns, n := namespaced(*v.path)
|
||||
env["POD"] = n
|
||||
env["NAMESPACE"] = ns
|
||||
log.Debug().Msgf("OVER ENV %#v", env)
|
||||
|
||||
return env
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func newDetailsView(app *appView, backFn ui.ActionHandler) *detailsView {
|
|||
v.SetTitleColor(tcell.ColorAqua)
|
||||
v.SetInputCapture(v.keyboard)
|
||||
|
||||
v.cmdBuff = ui.NewCmdBuff('/')
|
||||
v.cmdBuff = ui.NewCmdBuff('/', ui.FilterBuff)
|
||||
v.cmdBuff.AddListener(app.Cmd())
|
||||
v.cmdBuff.Reset()
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
type K9sEnv map[string]string
|
||||
|
||||
// EnvRX match $XXX custom arg.
|
||||
var envRX = regexp.MustCompile(`\A\$([\w|-]+)`)
|
||||
var envRX = regexp.MustCompile(`\A\$([\w]+)`)
|
||||
|
||||
func (e K9sEnv) envFor(n string) (string, error) {
|
||||
envs := envRX.FindStringSubmatch(n)
|
||||
|
|
@ -22,5 +22,5 @@ func (e K9sEnv) envFor(n string) (string, error) {
|
|||
return "", fmt.Errorf("No matching for %s", n)
|
||||
}
|
||||
|
||||
return env, nil
|
||||
return envRX.ReplaceAllString(n, env), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,16 +14,17 @@ func TestK9sEnv(t *testing.T) {
|
|||
err error
|
||||
e string
|
||||
}{
|
||||
"match": {q: "$A", err: nil, e: "10"},
|
||||
"match": {q: "$A", e: "10"},
|
||||
"noMatch": {q: "$BLEE", err: errors.New("No matching for $BLEE"), e: ""},
|
||||
"lower": {q: "$b", err: nil, e: "blee"},
|
||||
"dash": {q: "$col-0", err: nil, e: "fred"},
|
||||
"lower": {q: "$b", e: "blee"},
|
||||
"dash": {q: "$col0", e: "fred"},
|
||||
"mix": {q: "$col0-blee", e: "fred-blee"},
|
||||
}
|
||||
|
||||
e := K9sEnv{
|
||||
"A": "10",
|
||||
"B": "blee",
|
||||
"COL-0": "fred",
|
||||
"COL0": "fred",
|
||||
}
|
||||
|
||||
for k, u := range uu {
|
||||
|
|
|
|||
|
|
@ -130,16 +130,16 @@ func (v *logView) flush(index int, buff []string) {
|
|||
return
|
||||
}
|
||||
|
||||
v.log(strings.Join(buff[:index], "\n"))
|
||||
if atomic.LoadInt32(&v.autoScroll) == 1 {
|
||||
v.log(strings.Join(buff[:index], "\n"))
|
||||
v.app.QueueUpdateDraw(func() {
|
||||
v.update()
|
||||
v.updateIndicator()
|
||||
v.logs.ScrollToEnd()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (v *logView) update() {
|
||||
func (v *logView) updateIndicator() {
|
||||
status := "Off"
|
||||
if v.autoScroll == 1 {
|
||||
status = "On"
|
||||
|
|
@ -205,7 +205,7 @@ func (v *logView) toggleScrollCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
v.logs.LineUp()
|
||||
v.app.Flash().Info("Autoscroll is off.")
|
||||
}
|
||||
v.update()
|
||||
v.updateIndicator()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -334,7 +334,6 @@ func (v *resourceView) refresh() {
|
|||
if v.list.Namespaced() {
|
||||
v.list.SetNamespace(v.currentNS)
|
||||
}
|
||||
log.Debug().Msgf("Reconcile with NS %q", v.currentNS)
|
||||
if err := v.list.Reconcile(v.app.informer, v.path); err != nil {
|
||||
v.app.Flash().Err(err)
|
||||
}
|
||||
|
|
@ -347,7 +346,6 @@ func (v *resourceView) refresh() {
|
|||
|
||||
func (v *resourceView) namespaceActions(aa ui.KeyActions) {
|
||||
ns, err := v.app.Conn().Config().CurrentNamespaceName()
|
||||
log.Debug().Msgf("NAMESPACE %q -- %v", ns, err)
|
||||
if err == nil && ns != resource.AllNamespace {
|
||||
return
|
||||
}
|
||||
|
|
@ -401,7 +399,13 @@ func (v *resourceView) refreshActions() {
|
|||
}
|
||||
|
||||
func (v *resourceView) customActions(aa ui.KeyActions) {
|
||||
for k, plugin := range v.app.Config.K9s.Plugins {
|
||||
pp := config.NewPlugins()
|
||||
if err := pp.Load(); err != nil {
|
||||
log.Warn().Msgf("No plugin configuration found")
|
||||
return
|
||||
}
|
||||
|
||||
for k, plugin := range pp.Plugin {
|
||||
if !in(plugin.Scopes, v.list.GetName()) {
|
||||
continue
|
||||
}
|
||||
|
|
@ -428,10 +432,12 @@ func (v *resourceView) execCmd(bin string, bg bool, args ...string) ui.ActionHan
|
|||
return evt
|
||||
}
|
||||
|
||||
env := v.envFn()
|
||||
aa := make([]string, len(args))
|
||||
var (
|
||||
env = v.envFn()
|
||||
aa = make([]string, len(args))
|
||||
err error
|
||||
)
|
||||
for i, a := range args {
|
||||
var err error
|
||||
aa[i], err = env.envFor(a)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Args match failed")
|
||||
|
|
@ -454,12 +460,10 @@ func (v *resourceView) defaultK9sEnv() K9sEnv {
|
|||
"NAMESPACE": ns,
|
||||
"NAME": n,
|
||||
}
|
||||
|
||||
row := v.masterPage().GetRow()
|
||||
for i, r := range row {
|
||||
env["COL-"+strconv.Itoa(i)] = r
|
||||
env["COL"+strconv.Itoa(i)] = r
|
||||
}
|
||||
|
||||
log.Debug().Msgf("ENVs %#v", env)
|
||||
return env
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,7 @@ func newTableView(app *appView, title string) *tableView {
|
|||
}
|
||||
v.SearchBuff().AddListener(app.Cmd())
|
||||
v.SearchBuff().AddListener(&v)
|
||||
v.SearchBuff().Reset()
|
||||
|
||||
v.SearchBuff().Set(app.filter)
|
||||
v.bindKeys()
|
||||
|
||||
return &v
|
||||
|
|
@ -30,8 +29,8 @@ func newTableView(app *appView, title string) *tableView {
|
|||
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) BufferActive(state bool, k ui.BufferKind) {
|
||||
v.app.BufferActive(state, k)
|
||||
}
|
||||
|
||||
func (v *tableView) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
@ -46,6 +45,11 @@ func (v *tableView) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
|
||||
func (v *tableView) setFilterFn(fn func(string)) {
|
||||
v.filterFn = fn
|
||||
|
||||
cmd := v.SearchBuff().String()
|
||||
if isLabelSelector(cmd) && v.filterFn != nil {
|
||||
v.filterFn(trimLabelSelector(cmd))
|
||||
}
|
||||
}
|
||||
|
||||
func (v *tableView) bindKeys() {
|
||||
|
|
@ -70,6 +74,7 @@ func (v *tableView) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
|
||||
v.SearchBuff().SetActive(false)
|
||||
cmd := v.SearchBuff().String()
|
||||
v.app.filter = cmd
|
||||
if isLabelSelector(cmd) && v.filterFn != nil {
|
||||
v.filterFn(trimLabelSelector(cmd))
|
||||
return nil
|
||||
|
|
@ -91,6 +96,7 @@ func (v *tableView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
if !v.SearchBuff().Empty() {
|
||||
v.app.Flash().Info("Clearing filter...")
|
||||
}
|
||||
v.app.filter = ""
|
||||
if isLabelSelector(v.SearchBuff().String()) {
|
||||
v.filterFn("")
|
||||
}
|
||||
|
|
@ -106,11 +112,12 @@ func (v *tableView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
v.app.Flash().Info("Filter mode activated.")
|
||||
if isLabelSelector(v.SearchBuff().String()) {
|
||||
return nil
|
||||
}
|
||||
v.SearchBuff().Reset()
|
||||
// if isLabelSelector(v.SearchBuff().String()) {
|
||||
// return nil
|
||||
// }
|
||||
// v.SearchBuff().Reset()
|
||||
v.SearchBuff().SetActive(true)
|
||||
v.SearchBuff().Set(v.app.filter)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ func TestTableViewFilter(t *testing.T) {
|
|||
}
|
||||
v.Update(data)
|
||||
v.SearchBuff().SetActive(true)
|
||||
v.SearchBuff().Set([]rune("blee"))
|
||||
v.SearchBuff().Set("blee")
|
||||
v.filterCmd(nil)
|
||||
assert.Equal(t, 2, v.GetRowCount())
|
||||
v.resetCmd(nil)
|
||||
|
|
|
|||
Loading…
Reference in New Issue