From 42e5793438dbf65e077f78736d6a576ea9e7dc4b Mon Sep 17 00:00:00 2001 From: Bruno Meneguello Date: Fri, 11 Oct 2019 14:11:06 -0300 Subject: [PATCH] allow to mark multiple resources to act upon --- internal/config/style.go | 2 ++ internal/resource/list.go | 33 +++++++++++++++++++++++++++++++++ internal/ui/table.go | 14 ++++++++++++++ internal/views/pod.go | 16 +++++++++------- internal/views/resource.go | 36 ++++++++++++++++++++++++++++-------- skins/black_and_wtf.yml | 1 + skins/in_the_navy.yml | 1 + skins/stock.yml | 1 + 8 files changed, 89 insertions(+), 15 deletions(-) diff --git a/internal/config/style.go b/internal/config/style.go index cb276136..8f5175b1 100644 --- a/internal/config/style.go +++ b/internal/config/style.go @@ -99,6 +99,7 @@ type ( FgColor string `yaml:"fgColor"` BgColor string `yaml:"bgColor"` CursorColor string `yaml:"cursorColor"` + MarkColor string `yaml:"markColor"` Header TableHeader `yaml:"header"` } @@ -215,6 +216,7 @@ func newTable() Table { FgColor: "aqua", BgColor: "black", CursorColor: "aqua", + MarkColor: "darkgoldenrod", Header: newTableHeader(), } } diff --git a/internal/resource/list.go b/internal/resource/list.go index cf8c2620..ba645cf6 100644 --- a/internal/resource/list.go +++ b/internal/resource/list.go @@ -66,6 +66,7 @@ type ( Rows RowEvents NumCols map[string]bool Namespace string + Marks []string } // List protocol to display and update a collection of resources @@ -84,6 +85,7 @@ type ( SetFieldSelector(string) SetLabelSelector(string) HasSelectors() bool + ToggleMark(sk string) } // Columnar tracks resources that can be diplayed in a tabular fashion. @@ -127,9 +129,20 @@ type ( verbs int resource Resource cache RowEvents + marks []string } ) +// IsMarked checks if key is marked. +func (t *TableData) IsMarked(sk string) bool { + for _, mark := range t.Marks { + if mark == sk { + return true + } + } + return false +} + func newRowEvent(a watch.EventType, f, d Row) *RowEvent { return &RowEvent{Action: a, Fields: f, Deltas: d} } @@ -232,6 +245,7 @@ func (l *list) Data() TableData { Rows: l.cache, NumCols: l.resource.NumCols(l.namespace), Namespace: l.namespace, + Marks: l.marks, } } @@ -343,10 +357,29 @@ func (l *list) ensureDeletes(kk []string) { } if !found { delete(l.cache, k) + l.removeMark(k) } } } +func (l *list) removeMark(sk string) { + for index, mark := range l.marks { + if mark == sk { + l.marks = append(l.marks[:index], l.marks[index+1:]...) + } + } +} + +func (l *list) ToggleMark(sk string) { + for index, mark := range l.marks { + if mark == sk { + l.marks = append(l.marks[:index], l.marks[index+1:]...) + return + } + } + l.marks = append(l.marks, sk) +} + // Helpers... func computeDeltas(evt *RowEvent, newRow, deltas Row) watch.EventType { diff --git a/internal/ui/table.go b/internal/ui/table.go index f17e1c2a..c4a349c9 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -169,6 +169,16 @@ func (v *Table) GetSelectedItem() string { return v.selectedItem } +// GetSelectedItems return currently marked or selected items names. +func (v *Table) GetSelectedItems() []string { + if len(v.data.Marks) > 0 { + items := make([]string, len(v.data.Marks)) + copy(items, v.data.Marks) + return items + } + return []string{v.GetSelectedItem()} +} + func (v *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey { key := evt.Key() if key == tcell.KeyRune { @@ -318,6 +328,7 @@ func (v *Table) buildRow(row int, data resource.TableData, sk string, pads MaxyP if v.colorerFn != nil { f = v.colorerFn } + m := data.IsMarked(sk) for col, field := range data.Rows[sk].Fields { header := data.Header[col] field, align := v.formatCell(data.NumCols[header], header, field+Deltas(data.Rows[sk].Deltas[col], field), pads[col]) @@ -326,6 +337,9 @@ func (v *Table) buildRow(row int, data resource.TableData, sk string, pads MaxyP c.SetExpansion(1) c.SetAlign(align) c.SetTextColor(f(data.Namespace, data.Rows[sk])) + if m { + c.SetBackgroundColor(config.AsColor(v.styles.Table().MarkColor)) + } } v.SetCell(row, col, c) } diff --git a/internal/views/pod.go b/internal/views/pod.go index af2431ca..abe9455c 100644 --- a/internal/views/pod.go +++ b/internal/views/pod.go @@ -115,15 +115,17 @@ func (v *podView) killCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - sel := v.masterPage().GetSelectedItem() + sel := v.masterPage().GetSelectedItems() v.masterPage().ShowDeleted() - v.app.Flash().Infof("Delete resource %s %s", v.list.GetName(), sel) - if err := v.list.Resource().Delete(sel, true, false); err != nil { - v.app.Flash().Errf("Delete failed with %s", err) - } else { - deletePortForward(v.app.forwarders, sel) - v.refresh() + for _, res := range sel { + v.app.Flash().Infof("Delete resource %s %s", v.list.GetName(), res) + if err := v.list.Resource().Delete(res, true, false); err != nil { + v.app.Flash().Errf("Delete failed with %s", err) + } else { + deletePortForward(v.app.forwarders, res) + } } + v.refresh() return nil } diff --git a/internal/views/resource.go b/internal/views/resource.go index c1da42c0..4d62664a 100644 --- a/internal/views/resource.go +++ b/internal/views/resource.go @@ -192,23 +192,42 @@ func (v *resourceView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - sel := v.masterPage().GetSelectedItem() - msg := fmt.Sprintf("Delete %s %s?", v.list.GetName(), sel) + sel := v.masterPage().GetSelectedItems() + var msg string + if len(sel) > 1 { + msg = fmt.Sprintf("Delete %d selected %s?", len(sel), v.list.GetName()) + } else { + msg = fmt.Sprintf("Delete %s %s?", v.list.GetName(), sel) + } dialog.ShowDelete(v.Pages, msg, func(cascade, force bool) { v.masterPage().ShowDeleted() - v.app.Flash().Infof("Delete resource %s %s", v.list.GetName(), sel) - if err := v.list.Resource().Delete(sel, cascade, force); err != nil { - v.app.Flash().Errf("Delete failed with %s", err) - } else { - deletePortForward(v.app.forwarders, sel) - v.refresh() + for _, res := range sel { + v.app.Flash().Infof("Delete resource %s %s", v.list.GetName(), res) + if err := v.list.Resource().Delete(res, cascade, force); err != nil { + v.app.Flash().Errf("Delete failed with %s", err) + } else { + deletePortForward(v.app.forwarders, res) + } } + v.refresh() }, func() { v.switchPage("master") }) return nil } +func (v *resourceView) markCmd(evt *tcell.EventKey) *tcell.EventKey { + if !v.masterPage().RowSelected() { + return evt + } + + sel := v.masterPage().GetSelectedItem() + v.list.ToggleMark(sel) + v.refresh() + v.app.Draw() + return nil +} + func deletePortForward(ff map[string]forwarder, sel string) { for k, v := range ff { tokens := strings.Split(k, ":") @@ -371,6 +390,7 @@ func (v *resourceView) refreshActions() { tcell.KeyEnter: ui.NewKeyAction("Enter", v.enterCmd, false), tcell.KeyCtrlR: ui.NewKeyAction("Refresh", v.refreshCmd, false), } + aa[tcell.KeyCtrlSpace] = ui.NewKeyAction("Mark", v.markCmd, true) v.namespaceActions(aa) v.defaultActions(aa) diff --git a/skins/black_and_wtf.yml b/skins/black_and_wtf.yml index 4fe8393b..e8d91b1a 100644 --- a/skins/black_and_wtf.yml +++ b/skins/black_and_wtf.yml @@ -35,6 +35,7 @@ k9s: fgColor: white bgColor: black cursorColor: white + markColor: darkgoldenrod header: fgColor: darkgray bgColor: black diff --git a/skins/in_the_navy.yml b/skins/in_the_navy.yml index 0a7762d6..9dc0f062 100644 --- a/skins/in_the_navy.yml +++ b/skins/in_the_navy.yml @@ -37,6 +37,7 @@ k9s: fgColor: blue bgColor: darkblue cursorColor: aqua + markColor: mediumslateblue header: fgColor: white bgColor: darkblue diff --git a/skins/stock.yml b/skins/stock.yml index 6f42d942..be02aa01 100644 --- a/skins/stock.yml +++ b/skins/stock.yml @@ -35,6 +35,7 @@ k9s: fgColor: blue bgColor: black cursorColor: aqua + markColor: darkgoldenrod header: fgColor: white bgColor: black