bug fixes + cleanup

mine
derailed 2019-11-15 16:58:40 -07:00
parent 56e4dc9ba8
commit 162e3fe7ed
32 changed files with 339 additions and 269 deletions

View File

@ -216,7 +216,7 @@ func newTable() Table {
FgColor: "aqua",
BgColor: "black",
CursorColor: "aqua",
MarkColor: "darkgoldenrod",
MarkColor: "dodgerblue",
Header: newTableHeader(),
}
}

View File

@ -121,11 +121,14 @@ func (s *Stack) Peek() []Component {
return s.components
}
// Reset clear out the stack.
func (s *Stack) Reset() {
// ClearHistory clear out the stack history up to most recent.
func (s *Stack) ClearHistory() {
s.DumpStack()
top := s.Top()
for range s.components {
s.Pop()
}
s.Push(top)
}
// Empty returns true if the stack is empty.

View File

@ -57,7 +57,17 @@ func (v *Menu) StackTop(t model.Component) {
func (v *Menu) HydrateMenu(hh model.MenuHints) {
v.Clear()
sort.Sort(hh)
t := v.buildMenuTable(hh)
table := make([]model.MenuHints, maxRows+1)
colCount := (len(hh) / maxRows) + 1
if v.hasDigits(hh) {
colCount++
}
for row := 0; row < maxRows; row++ {
table[row] = make(model.MenuHints, colCount)
}
t := v.buildMenuTable(hh, table, colCount)
for row := 0; row < len(t); row++ {
for col := 0; col < len(t[row]); col++ {
if len(t[row][col]) == 0 {
@ -82,16 +92,7 @@ func (v *Menu) hasDigits(hh model.MenuHints) bool {
return false
}
func (v *Menu) buildMenuTable(hh model.MenuHints) [][]string {
table := make([][]string, maxRows+1)
colCount := (len(hh) / maxRows) + 1
if v.hasDigits(hh) {
colCount++
}
for row := 0; row < maxRows; row++ {
table[row] = make([]string, colCount)
}
func (v *Menu) buildMenuTable(hh model.MenuHints, table []model.MenuHints, colCount int) [][]string {
var row, col int
firstCmd := true
maxKeys := make([]int, colCount)
@ -102,21 +103,44 @@ func (v *Menu) buildMenuTable(hh model.MenuHints) [][]string {
if !menuRX.MatchString(h.Mnemonic) && firstCmd {
row, col, firstCmd = 0, col+1, false
if table[0][0] == "" {
if table[0][0].IsBlank() {
col = 0
}
}
if maxKeys[col] < len(h.Mnemonic) {
maxKeys[col] = len(h.Mnemonic)
}
table[row][col] = keyConv(v.formatMenu(h, maxKeys[col]))
table[row][col] = h
row++
if row >= maxRows {
row, col = 0, col+1
}
}
return table
out := make([][]string, len(table))
for r := range out {
out[r] = make([]string, len(table[r]))
}
v.layout(table, maxKeys, out)
return out
}
func (v *Menu) layout(table []model.MenuHints, mm []int, out [][]string) {
for r := range table {
for c := range table[r] {
out[r][c] = keyConv(v.formatMenu(table[r][c], mm[c]))
}
}
}
func (v *Menu) formatMenu(h model.MenuHint, size int) string {
i, err := strconv.Atoi(h.Mnemonic)
if err == nil {
return formatNSMenu(i, h.Description, v.styles.Frame())
}
return formatPlainMenu(h, size, v.styles.Frame())
}
// ----------------------------------------------------------------------------
@ -147,15 +171,6 @@ func toMnemonic(s string) string {
return "<" + keyConv(strings.ToLower(s)) + ">"
}
func (v *Menu) formatMenu(h model.MenuHint, size int) string {
i, err := strconv.Atoi(h.Mnemonic)
if err == nil {
return formatNSMenu(i, h.Description, v.styles.Frame())
}
return formatPlainMenu(h, size, v.styles.Frame())
}
func formatNSMenu(i int, name string, styles config.Frame) string {
fmat := strings.Replace(menuIndexFmt, "[key", "["+styles.Menu.NumKeyColor, 1)
fmat = strings.Replace(fmat, ":bg:", ":"+styles.Title.BgColor+":", -1)

164
internal/ui/select_table.go Normal file
View File

@ -0,0 +1,164 @@
package ui
import (
"path"
"strings"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
)
// Selectable represents a table with selections.
type SelectTable struct {
*tview.Table
ActiveNS string
selectedItem string
selectedRow int
selectedFn func(string) string
selListeners []SelectedRowFunc
marks map[string]bool
}
// ClearSelection reset selected row.
func (s *SelectTable) ClearSelection() {
s.Select(0, 0)
s.ScrollToBeginning()
}
// SelectFirstRow select first data row if any.
func (s *SelectTable) SelectFirstRow() {
if s.GetRowCount() > 0 {
s.Select(1, 0)
}
}
// GetSelectedItems return currently marked or selected items names.
func (s *SelectTable) GetSelectedItems() []string {
if len(s.marks) == 0 {
return []string{s.GetSelectedItem()}
}
var items []string
for item, marked := range s.marks {
if marked {
items = append(items, item)
}
}
return items
}
// GetSelectedItem returns the currently selected item name.
func (s *SelectTable) GetSelectedItem() string {
if s.selectedFn != nil {
return s.selectedFn(s.selectedItem)
}
return s.selectedItem
}
// GetSelectedCell returns the content of a cell for the currently selected row.
func (s *SelectTable) GetSelectedCell(col int) string {
return TrimCell(s, s.selectedRow, col)
}
// SetSelectedFn defines a function that cleanse the current selection.
func (s *SelectTable) SetSelectedFn(f func(string) string) {
s.selectedFn = f
}
// GetSelectedRow fetch the currently selected row index.
func (s *SelectTable) GetSelectedRowIndex() int {
return s.selectedRow
}
// RowSelected checks if there is an active row selection.
func (s *SelectTable) RowSelected() bool {
return s.selectedItem != ""
}
// GetRow retrieves the entire selected row.
func (s *SelectTable) GetRow() resource.Row {
r := make(resource.Row, s.GetColumnCount())
for i := 0; i < s.GetColumnCount(); i++ {
c := s.GetCell(s.selectedRow, i)
r[i] = strings.TrimSpace(c.Text)
}
return r
}
func (s *SelectTable) updateSelectedItem(r int) {
if r == 0 || s.GetCell(r, 0) == nil {
s.selectedItem = ""
return
}
col0 := TrimCell(s, r, 0)
switch s.ActiveNS {
case resource.NotNamespaced:
s.selectedItem = col0
case resource.AllNamespace, resource.AllNamespaces:
s.selectedItem = path.Join(col0, TrimCell(s, r, 1))
default:
s.selectedItem = path.Join(s.ActiveNS, col0)
}
}
// SelectRow select a given row by index.
func (s *SelectTable) SelectRow(r int, broadcast bool) {
if !broadcast {
s.SetSelectionChangedFunc(nil)
}
defer s.SetSelectionChangedFunc(s.selChanged)
s.Select(r, 0)
s.updateSelectedItem(r)
}
// UpdateSelection refresh selected row.
func (s *SelectTable) updateSelection(broadcast bool) {
s.SelectRow(s.selectedRow, broadcast)
}
func (s *SelectTable) selChanged(r, c int) {
s.selectedRow = r
s.updateSelectedItem(r)
if r == 0 {
return
}
if s.marks[s.GetSelectedItem()] {
s.SetSelectedStyle(tcell.ColorBlack, tcell.ColorCadetBlue, tcell.AttrBold)
} else {
cell := s.GetCell(r, c)
s.SetSelectedStyle(tcell.ColorBlack, cell.Color, tcell.AttrBold)
}
for _, f := range s.selListeners {
f(r, c)
}
}
// ToggleMark toggles marked row
func (s *SelectTable) ToggleMark() {
s.marks[s.GetSelectedItem()] = !s.marks[s.GetSelectedItem()]
if !s.marks[s.GetSelectedItem()] {
return
}
log.Debug().Msgf("YO!!!!")
s.SetSelectedStyle(
tcell.ColorBlack,
tcell.ColorViolet,
tcell.AttrBold,
)
}
func (s *Table) IsMarked(item string) bool {
return s.marks[item]
}
// AddSelectedRowListener add a new selected row listener.
func (s *SelectTable) AddSelectedRowListener(f SelectedRowFunc) {
s.selListeners = append(s.selListeners, f)
}

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"path"
"regexp"
"strings"
"time"
@ -29,44 +28,32 @@ type (
// Table represents tabular data.
type Table struct {
*tview.Table
*SelectTable
baseTitle string
data resource.TableData
actions KeyActions
cmdBuff *CmdBuff
styles *config.Styles
activeNS string
sortCol SortColumn
sortFn SortFn
colorerFn ColorerFunc
selectedItem string
selectedRow int
selectedFn func(string) string
selListeners []SelectedRowFunc
marks map[string]bool
baseTitle string
Data resource.TableData
actions KeyActions
cmdBuff *CmdBuff
styles *config.Styles
colorerFn ColorerFunc
sortCol SortColumn
sortFn SortFn
}
// NewTable returns a new table view.
func NewTable(title string) *Table {
return &Table{
Table: tview.NewTable(),
SelectTable: &SelectTable{
Table: tview.NewTable(),
marks: make(map[string]bool),
},
actions: make(KeyActions),
cmdBuff: NewCmdBuff('/', FilterBuff),
baseTitle: title,
sortCol: SortColumn{0, 0, true},
marks: make(map[string]bool),
}
}
func mustExtractSyles(ctx context.Context) *config.Styles {
styles, ok := ctx.Value(KeyStyles).(*config.Styles)
if !ok {
log.Fatal().Msg("Expecting valid styles")
}
return styles
}
func (t *Table) Init(ctx context.Context) {
t.styles = mustExtractSyles(ctx)
@ -93,114 +80,6 @@ func (t *Table) SendKey(evt *tcell.EventKey) {
t.keyboard(evt)
}
// GetRow retrieves the entire selected row.
func (t *Table) GetRow() resource.Row {
r := make(resource.Row, t.GetColumnCount())
for i := 0; i < t.GetColumnCount(); i++ {
c := t.GetCell(t.selectedRow, i)
r[i] = strings.TrimSpace(c.Text)
}
return r
}
// AddSelectedRowListener add a new selected row listener.
func (t *Table) AddSelectedRowListener(f SelectedRowFunc) {
t.selListeners = append(t.selListeners, f)
}
func (t *Table) selChanged(r, c int) {
t.selectedRow = r
t.updateSelectedItem(r)
if r == 0 {
return
}
cell := t.GetCell(r, c)
t.SetSelectedStyle(
tcell.ColorBlack,
cell.Color,
tcell.AttrBold,
)
for _, f := range t.selListeners {
f(r, c)
}
}
// UpdateSelection refresh selected row.
func (t *Table) updateSelection(broadcast bool) {
t.SelectRow(t.selectedRow, broadcast)
}
// SelectRow select a given row by index.
func (t *Table) SelectRow(r int, broadcast bool) {
if !broadcast {
t.SetSelectionChangedFunc(nil)
}
defer t.SetSelectionChangedFunc(t.selChanged)
t.Select(r, 0)
t.updateSelectedItem(r)
}
func (t *Table) updateSelectedItem(r int) {
if r == 0 || t.GetCell(r, 0) == nil {
t.selectedItem = ""
return
}
col0 := TrimCell(t, r, 0)
switch t.activeNS {
case resource.NotNamespaced:
t.selectedItem = col0
case resource.AllNamespace, resource.AllNamespaces:
t.selectedItem = path.Join(col0, TrimCell(t, r, 1))
default:
t.selectedItem = path.Join(t.activeNS, col0)
}
}
// SetSelectedFn defines a function that cleanse the current selection.
func (t *Table) SetSelectedFn(f func(string) string) {
t.selectedFn = f
}
// RowSelected checks if there is an active row selection.
func (t *Table) RowSelected() bool {
return t.selectedItem != ""
}
// GetSelectedCell returns the content of a cell for the currently selected row.
func (t *Table) GetSelectedCell(col int) string {
return TrimCell(t, t.selectedRow, col)
}
// GetSelectedRow fetch the currently selected row index.
func (t *Table) GetSelectedRowIndex() int {
return t.selectedRow
}
// GetSelectedItem returns the currently selected item name.
func (t *Table) GetSelectedItem() string {
if t.selectedFn != nil {
return t.selectedFn(t.selectedItem)
}
return t.selectedItem
}
// GetSelectedItems return currently marked or selected items names.
func (t *Table) GetSelectedItems() []string {
if len(t.marks) > 0 {
var items []string
for item, marked := range t.marks {
if marked {
items = append(items, item)
}
}
return items
}
return []string{t.GetSelectedItem()}
}
func (t *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey {
key := evt.Key()
if key == tcell.KeyRune {
@ -222,11 +101,6 @@ func (t *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
// GetData fetch tabular data.
func (t *Table) GetData() resource.TableData {
return t.data
}
// GetFilteredData fetch filtered tabular data.
func (t *Table) GetFilteredData() resource.TableData {
return t.filtered()
@ -247,16 +121,6 @@ func (t *Table) SetColorerFn(f ColorerFunc) {
t.colorerFn = f
}
// ActiveNS get the resource namespace.
func (t *Table) ActiveNS() string {
return t.activeNS
}
// SetActiveNS set the resource namespace.
func (t *Table) SetActiveNS(ns string) {
t.activeNS = ns
}
// SetSortCol sets in sort column index and order.
func (t *Table) SetSortCol(index, count int, asc bool) {
t.sortCol.index, t.sortCol.colCount, t.sortCol.asc = index, count, asc
@ -264,9 +128,9 @@ func (t *Table) SetSortCol(index, count int, asc bool) {
// Update table content.
func (t *Table) Update(data resource.TableData) {
t.data = data
t.Data = data
if t.cmdBuff.Empty() {
t.doUpdate(t.data)
t.doUpdate(t.Data)
} else {
t.doUpdate(t.filtered())
}
@ -275,8 +139,8 @@ func (t *Table) Update(data resource.TableData) {
}
func (t *Table) doUpdate(data resource.TableData) {
t.activeNS = data.Namespace
if t.activeNS == resource.AllNamespaces && t.activeNS != "*" {
t.ActiveNS = data.Namespace
if t.ActiveNS == resource.AllNamespaces && t.ActiveNS != "*" {
t.actions[KeyShiftP] = NewKeyAction("Sort Namespace", t.SortColCmd(-2), false)
} else {
delete(t.actions, KeyShiftP)
@ -348,6 +212,13 @@ func (t *Table) sort(data resource.TableData, row int) {
row++
}
}
// check marks if a row is deleted make sure we blow the mark too.
for k := range t.marks {
if _, ok := t.Data.Rows[k]; !ok {
delete(t.marks, k)
}
}
}
func (t *Table) buildRow(row int, data resource.TableData, sk string, pads MaxyPad) {
@ -355,7 +226,7 @@ func (t *Table) buildRow(row int, data resource.TableData, sk string, pads MaxyP
if t.colorerFn != nil {
f = t.colorerFn
}
m := t.isMarked(sk)
m := t.IsMarked(sk)
for col, field := range data.Rows[sk].Fields {
header := data.Header[col]
cell, align := t.formatCell(data.NumCols[header], header, field+Deltas(data.Rows[sk].Deltas[col], field), pads[col])
@ -392,15 +263,20 @@ func (t *Table) formatCell(numerical bool, header, field string, padding int) (s
return field, align
}
func (t *Table) ClearMarks() {
t.marks = map[string]bool{}
t.Refresh()
}
// Refresh update the table data.
func (t *Table) Refresh() {
t.Update(t.data)
t.Update(t.Data)
}
// NameColIndex returns the index of the resource name column.
func (t *Table) NameColIndex() int {
col := 0
if t.activeNS == resource.AllNamespaces {
if t.ActiveNS == resource.AllNamespaces {
col++
}
return col
@ -418,7 +294,7 @@ func (t *Table) AddHeaderCell(numerical bool, col int, name string) {
func (t *Table) filtered() resource.TableData {
if t.cmdBuff.Empty() || IsLabelSelector(t.cmdBuff.String()) {
return t.data
return t.Data
}
q := t.cmdBuff.String()
@ -434,15 +310,15 @@ func (t *Table) rxFilter() resource.TableData {
if err != nil {
log.Error().Err(errors.New("Invalid filter expression")).Msg("Regexp")
t.cmdBuff.Clear()
return t.data
return t.Data
}
filtered := resource.TableData{
Header: t.data.Header,
Header: t.Data.Header,
Rows: resource.RowEvents{},
Namespace: t.data.Namespace,
Namespace: t.Data.Namespace,
}
for k, row := range t.data.Rows {
for k, row := range t.Data.Rows {
f := strings.Join(row.Fields, " ")
if rx.MatchString(f) {
filtered.Rows[k] = row
@ -454,19 +330,19 @@ func (t *Table) rxFilter() resource.TableData {
func (t *Table) fuzzyFilter(q string) resource.TableData {
var ss, kk []string
for k, row := range t.data.Rows {
for k, row := range t.Data.Rows {
ss = append(ss, row.Fields[t.NameColIndex()])
kk = append(kk, k)
}
filtered := resource.TableData{
Header: t.data.Header,
Header: t.Data.Header,
Rows: resource.RowEvents{},
Namespace: t.data.Namespace,
Namespace: t.Data.Namespace,
}
mm := fuzzy.Find(q, ss)
for _, m := range mm {
filtered.Rows[kk[m.Index]] = t.data.Rows[kk[m.Index]]
filtered.Rows[kk[m.Index]] = t.Data.Rows[kk[m.Index]]
}
return filtered
@ -482,19 +358,6 @@ func (t *Table) SearchBuff() *CmdBuff {
return t.cmdBuff
}
// ClearSelection reset selected row.
func (t *Table) ClearSelection() {
t.Select(0, 0)
t.ScrollToBeginning()
}
// SelectFirstRow select first data row if any.
func (t *Table) SelectFirstRow() {
if t.GetRowCount() > 0 {
t.Select(1, 0)
}
}
// ShowDeleted marks row as deleted.
func (t *Table) ShowDeleted() {
r, _ := t.GetSelection()
@ -535,11 +398,11 @@ func (t *Table) UpdateTitle() {
if rc > 0 {
rc--
}
switch t.activeNS {
switch t.ActiveNS {
case resource.NotNamespaced, "*":
title = skinTitle(fmt.Sprintf(titleFmt, t.baseTitle, rc), t.styles.Frame())
default:
ns := t.activeNS
ns := t.ActiveNS
if ns == resource.AllNamespaces {
ns = resource.AllNamespace
}
@ -563,12 +426,3 @@ func (t *Table) SortInvertCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
// ToggleMark toggles marked row
func (t *Table) ToggleMark() {
t.marks[t.GetSelectedItem()] = !t.marks[t.GetSelectedItem()]
}
func (t *Table) isMarked(item string) bool {
return t.marks[item]
}

View File

@ -1,6 +1,7 @@
package ui
import (
"context"
"fmt"
"regexp"
"sort"
@ -37,8 +38,16 @@ var (
fuzzyCmd = regexp.MustCompile(`\A\-f`)
)
func mustExtractSyles(ctx context.Context) *config.Styles {
styles, ok := ctx.Value(KeyStyles).(*config.Styles)
if !ok {
log.Fatal().Msg("Expecting valid styles")
}
return styles
}
// TrimCell removes superfluous padding.
func TrimCell(tv *Table, row, col int) string {
func TrimCell(tv *SelectTable, row, col int) string {
c := tv.GetCell(row, col)
if c == nil {
log.Error().Err(fmt.Errorf("No cell at location [%d:%d]", row, col)).Msg("Trim cell failed!")

View File

@ -35,7 +35,7 @@ func (a *Alias) Init(ctx context.Context) {
a.SetBorderFocusColor(tcell.ColorMediumSpringGreen)
a.SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone)
a.SetColorerFn(aliasColorer)
a.SetActiveNS("")
a.ActiveNS = resource.AllNamespaces
a.registerActions()
a.Update(a.hydrate())
@ -53,6 +53,8 @@ func (a *Alias) registerActions() {
a.RmAction(ui.KeyShiftA)
a.RmAction(ui.KeyShiftN)
a.RmAction(tcell.KeyCtrlS)
a.RmAction(tcell.KeyCtrlSpace)
a.RmAction(ui.KeySpace)
a.AddActions(ui.KeyActions{
tcell.KeyEnter: ui.NewKeyAction("Goto Resource", a.gotoCmd, true),
@ -76,7 +78,7 @@ func (a *Alias) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
r, _ := a.GetSelection()
if r != 0 {
s := ui.TrimCell(a.Table.Table, r, 1)
s := ui.TrimCell(a.Table.SelectTable, r, 1)
tokens := strings.Split(s, ",")
a.app.Content.Pop()
if !a.app.gotoResource(tokens[0]) {

View File

@ -399,9 +399,8 @@ func (a *App) toggleHeaderCmd(evt *tcell.EventKey) *tcell.EventKey {
func (a *App) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.CmdBuff().IsActive() && !a.CmdBuff().Empty() {
a.Content.Stack.Reset()
if !a.gotoResource(a.GetCmd()) {
a.Flash().Errf("Goto %s failed!", a.GetCmd())
if a.gotoResource(a.GetCmd()) {
a.Content.Stack.ClearHistory()
}
a.ResetCmd()
return nil

View File

@ -148,7 +148,7 @@ func (b *Bench) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
func (b *Bench) benchFile() string {
r := b.masterPage().GetSelectedRowIndex()
return ui.TrimCell(b.masterPage().Table, r, 7)
return ui.TrimCell(b.masterPage().SelectTable, r, 7)
}
func (b *Bench) hydrate() resource.TableData {

View File

@ -52,6 +52,8 @@ func (c *Container) Name() string { return "containers" }
func (c *Container) extraActions(aa ui.KeyActions) {
c.LogResource.extraActions(aa)
c.masterPage().RmAction(tcell.KeyCtrlSpace)
c.masterPage().RmAction(ui.KeySpace)
aa[ui.KeyShiftF] = ui.NewKeyAction("PortForward", c.portFwdCmd, true)
aa[ui.KeyShiftL] = ui.NewKeyAction("Logs Previous", c.prevLogsCmd, true)

View File

@ -13,5 +13,5 @@ func TestContainerNew(t *testing.T) {
po.Init(makeCtx())
assert.Equal(t, "containers", po.Name())
assert.Equal(t, 22, len(po.Hints()))
assert.Equal(t, 21, len(po.Hints()))
}

View File

@ -7,6 +7,7 @@ import (
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/ui"
"github.com/gdamore/tcell"
)
// Context presents a context viewer.
@ -31,6 +32,8 @@ func (c *Context) Init(ctx context.Context) {
func (c *Context) extraActions(aa ui.KeyActions) {
c.masterPage().RmAction(ui.KeyShiftA)
c.masterPage().RmAction(tcell.KeyCtrlSpace)
c.masterPage().RmAction(ui.KeySpace)
}
func (c *Context) useCtx(app *App, _, res, sel string) {

View File

@ -13,5 +13,5 @@ func TestContext(t *testing.T) {
ctx.Init(makeCtx())
assert.Equal(t, "ctx", ctx.Name())
assert.Equal(t, 13, len(ctx.Hints()))
assert.Equal(t, 12, len(ctx.Hints()))
}

View File

@ -13,6 +13,6 @@ func TestDeploy(t *testing.T) {
v.Init(makeCtx())
assert.Equal(t, "deploy", v.Name())
assert.Equal(t, 24, len(v.Hints()))
assert.Equal(t, 25, len(v.Hints()))
}

View File

@ -13,5 +13,5 @@ func TestDaemonSet(t *testing.T) {
v.Init(makeCtx())
assert.Equal(t, "ds", v.Name())
assert.Equal(t, 23, len(v.Hints()))
assert.Equal(t, 24, len(v.Hints()))
}

View File

@ -57,6 +57,9 @@ func (v *Help) Hints() model.MenuHints {
}
func (v *Help) bindKeys() {
v.RmAction(tcell.KeyCtrlSpace)
v.RmAction(ui.KeySpace)
v.actions = ui.KeyActions{
tcell.KeyEsc: ui.NewKeyAction("Back", v.backCmd, true),
tcell.KeyEnter: ui.NewKeyAction("Back", v.backCmd, false),

View File

@ -20,7 +20,7 @@ func TestHelpNew(t *testing.T) {
v := view.NewHelp()
v.Init(ctx)
assert.Equal(t, 32, v.GetRowCount())
assert.Equal(t, 33, v.GetRowCount())
assert.Equal(t, 10, v.GetColumnCount())
assert.Equal(t, "<esc>", v.GetCell(1, 0).Text)
assert.Equal(t, "Back", v.GetCell(1, 1).Text)

View File

@ -13,5 +13,5 @@ func TestNSCleanser(t *testing.T) {
ns.Init(makeCtx())
assert.Equal(t, "ns", ns.Name())
assert.Equal(t, 20, len(ns.Hints()))
assert.Equal(t, 21, len(ns.Hints()))
}

View File

@ -16,7 +16,7 @@ func TestPodNew(t *testing.T) {
po.Init(makeCtx())
assert.Equal(t, "pods", po.Name())
assert.Equal(t, 31, len(po.Hints()))
assert.Equal(t, 32, len(po.Hints()))
}
// Helpers...

View File

@ -55,9 +55,9 @@ func (p *Policy) Init(ctx context.Context) {
p.bindKeys()
p.SetSortCol(1, len(rbacHeader), false)
p.Start()
p.refresh()
p.SelectRow(1, true)
p.Start()
}
func (p *Policy) Name() string {
@ -65,6 +65,7 @@ func (p *Policy) Name() string {
}
func (p *Policy) Start() {
p.Stop()
ctx, cancel := context.WithCancel(context.Background())
p.cancel = cancel
go func(ctx context.Context) {
@ -74,7 +75,6 @@ func (p *Policy) Start() {
return
case <-time.After(time.Duration(p.app.Config.K9s.GetRefreshRate()) * time.Second):
p.refresh()
p.app.Draw()
}
}
}(ctx)
@ -88,6 +88,8 @@ func (p *Policy) Stop() {
func (p *Policy) bindKeys() {
p.RmAction(ui.KeyShiftA)
p.RmAction(tcell.KeyCtrlSpace)
p.RmAction(ui.KeySpace)
p.AddActions(ui.KeyActions{
tcell.KeyEscape: ui.NewKeyAction("Back", p.resetCmd, false),
@ -109,7 +111,9 @@ func (p *Policy) refresh() {
log.Error().Err(err).Msgf("Refresh for %s:%s", p.subjectKind, p.subjectName)
p.app.Flash().Err(err)
}
p.Update(data)
p.app.QueueUpdateDraw(func() {
p.Update(data)
})
}
func (p *Policy) resetCmd(evt *tcell.EventKey) *tcell.EventKey {

View File

@ -48,7 +48,7 @@ func (p *PortForward) Init(ctx context.Context) {
tv.SetBorderFocusColor(tcell.ColorDodgerBlue)
tv.SetSelectedStyle(tcell.ColorWhite, tcell.ColorDodgerBlue, tcell.AttrNone)
tv.SetColorerFn(forwardColorer)
tv.SetActiveNS("")
tv.ActiveNS = resource.AllNamespaces
tv.SetSortCol(tv.NameColIndex()+6, 0, true)
tv.Select(1, 0)
@ -136,13 +136,13 @@ func (p *PortForward) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
tv := p.masterPage()
r, _ := tv.GetSelection()
cfg, co := defaultConfig(), ui.TrimCell(tv.Table, r, 2)
cfg, co := defaultConfig(), ui.TrimCell(tv.SelectTable, r, 2)
if b, ok := p.app.Bench.Benchmarks.Containers[containerID(sel, co)]; ok {
cfg = b
}
cfg.Name = sel
base := ui.TrimCell(tv.Table, r, 4)
base := ui.TrimCell(tv.SelectTable, r, 4)
var err error
if p.bench, err = perf.NewBenchmark(base, cfg); err != nil {
p.app.Flash().Errf("Bench failed %v", err)
@ -183,8 +183,8 @@ func (p *PortForward) getSelectedItem() string {
return ""
}
return fwFQN(
fqn(ui.TrimCell(tv.Table, r, 0), ui.TrimCell(tv.Table, r, 1)),
ui.TrimCell(tv.Table, r, 2),
fqn(ui.TrimCell(tv.SelectTable, r, 0), ui.TrimCell(tv.SelectTable, r, 1)),
ui.TrimCell(tv.SelectTable, r, 2),
)
}

View File

@ -12,5 +12,5 @@ func TestPortForwardNew(t *testing.T) {
po.Init(makeCtx())
assert.Equal(t, "PortForwards", po.Name())
assert.Equal(t, 15, len(po.Hints()))
assert.Equal(t, 17, len(po.Hints()))
}

View File

@ -82,7 +82,7 @@ func NewRbac(app *App, ns, name string, kind roleKind) *Rbac {
// Init initializes the view.
func (r *Rbac) Init(ctx context.Context) {
r.SetActiveNS(r.app.Config.ActiveNamespace())
r.ActiveNS = r.app.Config.ActiveNamespace()
r.SetColorerFn(rbacColorer)
r.Table.Init(ctx)
r.bindKeys()
@ -131,6 +131,8 @@ func (r *Rbac) Name() string {
func (r *Rbac) bindKeys() {
r.RmAction(ui.KeyShiftA)
r.RmAction(tcell.KeyCtrlSpace)
r.RmAction(ui.KeySpace)
r.AddActions(ui.KeyActions{
tcell.KeyEscape: ui.NewKeyAction("Reset", r.resetCmd, false),
@ -156,7 +158,6 @@ func (r *Rbac) refresh() {
}
func (r *Rbac) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
log.Debug().Msgf("!!!YO!!!!")
if !r.SearchBuff().Empty() {
r.SearchBuff().Reset()
return nil

View File

@ -169,14 +169,14 @@ func (r *Resource) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
sel := r.masterPage().GetSelectedItems()
var msg string
if len(sel) > 1 {
msg = fmt.Sprintf("Delete %d selected %s?", len(sel), r.list.GetName())
msg = fmt.Sprintf("Delete %d marked %s?", len(sel), r.list.GetName())
} else {
msg = fmt.Sprintf("Delete %s %s?", r.list.GetName(), sel[0])
}
dialog.ShowDelete(r.Pages, msg, func(cascade, force bool) {
r.masterPage().ShowDeleted()
if len(sel) > 1 {
r.app.Flash().Infof("Delete %d selected %s", len(sel), r.list.GetName())
r.app.Flash().Infof("Delete %d marked %s", len(sel), r.list.GetName())
} else {
r.app.Flash().Infof("Delete resource %s %s", r.list.GetName(), sel[0])
}
@ -192,17 +192,6 @@ func (r *Resource) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
func (r *Resource) markCmd(evt *tcell.EventKey) *tcell.EventKey {
if !r.masterPage().RowSelected() {
return evt
}
r.masterPage().ToggleMark()
r.refresh()
r.app.Draw()
return nil
}
func deletePortForward(ff map[string]forwarder, sel string) {
for k, f := range ff {
tokens := strings.Split(k, ":")
@ -373,7 +362,6 @@ func (r *Resource) refreshActions() {
tcell.KeyEnter: ui.NewKeyAction("Enter", r.enterCmd, false),
tcell.KeyCtrlR: ui.NewKeyAction("Refresh", r.refreshCmd, false),
}
aa[ui.KeySpace] = ui.NewKeyAction("Mark", r.markCmd, true)
r.namespaceActions(aa)
r.defaultActions(aa)

View File

@ -48,7 +48,7 @@ func (s *ScreenDump) Init(ctx context.Context) {
table.SetBorderFocusColor(tcell.ColorSteelBlue)
table.SetSelectedStyle(tcell.ColorWhite, tcell.ColorRoyalBlue, tcell.AttrNone)
table.SetColorerFn(dumpColorer)
table.SetActiveNS(resource.AllNamespaces)
table.ActiveNS = resource.AllNamespaces
table.SetSortCol(table.NameColIndex(), 0, true)
table.SelectRow(1, true)
}

View File

@ -12,5 +12,5 @@ func TestScreenDumpNew(t *testing.T) {
po.Init(makeCtx())
assert.Equal(t, "Screen Dumps", po.Name())
assert.Equal(t, 11, len(po.Hints()))
assert.Equal(t, 13, len(po.Hints()))
}

View File

@ -13,5 +13,5 @@ func TestSecretNew(t *testing.T) {
s.Init(makeCtx())
assert.Equal(t, "secrets", s.Name())
assert.Equal(t, 19, len(s.Hints()))
assert.Equal(t, 20, len(s.Hints()))
}

View File

@ -13,5 +13,5 @@ func TestStatefulSetNew(t *testing.T) {
s.Init(makeCtx())
assert.Equal(t, "sts", s.Name())
assert.Equal(t, 24, len(s.Hints()))
assert.Equal(t, 25, len(s.Hints()))
}

View File

@ -2,8 +2,8 @@ package view
import (
"context"
"fmt"
"reflect"
"strings"
"time"
"github.com/derailed/k9s/internal/resource"
@ -42,7 +42,7 @@ func NewSubject(title, gvr string, list resource.List) ResourceViewer {
// Init initializes the view.
func (s *Subject) Init(ctx context.Context) {
s.SetActiveNS("*")
s.ActiveNS = "*"
s.SetColorerFn(rbacColorer)
s.Table.Init(ctx)
s.bindKeys()
@ -66,8 +66,9 @@ func (s *Subject) Start() {
log.Debug().Msgf("Subject:%s Watch bailing out!", s.subjectKind)
return
case <-time.After(time.Duration(s.app.Config.K9s.GetRefreshRate()) * time.Second):
s.refresh()
s.app.Draw()
s.app.QueueUpdateDraw(func() {
s.refresh()
})
}
}
}(ctx)
@ -88,9 +89,10 @@ func (s *Subject) masterPage() *Table {
}
func (s *Subject) bindKeys() {
// No time data or ns
s.RmAction(ui.KeyShiftA)
s.RmAction(ui.KeyShiftP)
s.RmAction(tcell.KeyCtrlSpace)
s.RmAction(ui.KeySpace)
s.AddActions(ui.KeyActions{
tcell.KeyEnter: ui.NewKeyAction("Policies", s.policyCmd, true),
@ -119,7 +121,6 @@ func (s *Subject) refresh() {
}
func (s *Subject) policyCmd(evt *tcell.EventKey) *tcell.EventKey {
log.Debug().Msg("YO!!")
if !s.RowSelected() {
return evt
}
@ -296,12 +297,14 @@ func mapCmdSubject(subject string) string {
}
func mapFuSubject(subject string) string {
switch strings.ToLower(subject) {
switch subject {
case group:
return "g"
case sa:
return "s"
default:
case user:
return "u"
default:
panic(fmt.Sprintf("Unknown FU subject %q", subject))
}
}

View File

@ -13,5 +13,5 @@ func TestServiceNew(t *testing.T) {
s.Init(makeCtx())
assert.Equal(t, "svc", s.Name())
assert.Equal(t, 22, len(s.Hints()))
assert.Equal(t, 23, len(s.Hints()))
}

View File

@ -23,7 +23,6 @@ func NewTable(title string) *Table {
func (t *Table) Init(ctx context.Context) {
t.app = mustExtractApp(ctx)
ctx = context.WithValue(ctx, ui.KeyStyles, t.app.Styles)
t.Table.Init(ctx)
@ -65,6 +64,8 @@ func (t *Table) setFilterFn(fn func(string)) {
func (t *Table) bindKeys() {
t.AddActions(ui.KeyActions{
ui.KeySpace: ui.NewKeyAction("Mark", t.markCmd, true),
tcell.KeyCtrlSpace: ui.NewKeyAction("Marks Clear", t.clearMarksCmd, true),
tcell.KeyCtrlS: ui.NewKeyAction("Save", t.saveCmd, true),
ui.KeySlash: ui.NewKeyAction("Filter Mode", t.activateCmd, false),
tcell.KeyEscape: ui.NewKeyAction("Filter Reset", t.resetCmd, false),
@ -78,6 +79,25 @@ func (t *Table) bindKeys() {
})
}
func (t *Table) markCmd(evt *tcell.EventKey) *tcell.EventKey {
if !t.RowSelected() {
return evt
}
t.ToggleMark()
t.Refresh()
return nil
}
func (t *Table) clearMarksCmd(evt *tcell.EventKey) *tcell.EventKey {
if !t.RowSelected() {
return evt
}
t.ClearMarks()
return nil
}
func (t *Table) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
if !t.SearchBuff().IsActive() {
return evt

View File

@ -15,7 +15,7 @@ import (
)
func trimCellRelative(t *Table, row, col int) string {
return ui.TrimCell(t.Table, row, t.NameColIndex()+col)
return ui.TrimCell(t.SelectTable, row, t.NameColIndex()+col)
}
func saveTable(cluster, name string, data resource.TableData) (string, error) {