checkpoint

mine
derailed 2019-03-27 13:37:34 -06:00
parent 0a6caa2d54
commit fec714f0ca
11 changed files with 585 additions and 50 deletions

View File

@ -4,11 +4,9 @@ import (
"context"
"fmt"
"strings"
"time"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
)
const (
@ -30,28 +28,8 @@ func newAliasView(app *appView) *aliasView {
v.colorerFn = aliasColorer
v.current = app.content.GetPrimitive("main").(igniter)
v.currentNS = ""
v.registerActions()
}
v.actions[tcell.KeyEnter] = newKeyAction("Goto", v.gotoCmd, true)
v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.resetCmd, false)
v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd, false)
v.actions[KeyShiftR] = newKeyAction("Sort Resources", v.sortResourceCmd, true)
v.actions[KeyShiftO] = newKeyAction("Sort Groups", v.sortGroupCmd, true)
ctx, cancel := context.WithCancel(context.TODO())
v.cancel = cancel
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
log.Debug().Msg("Alias GR bailing out!")
return
case <-time.After(1 * time.Second):
v.update(v.hydrate())
v.app.Draw()
}
}
}(ctx)
return &v
}
@ -62,6 +40,14 @@ func (v *aliasView) init(context.Context, string) {
v.resetTitle()
}
func (v *aliasView) registerActions() {
v.actions[tcell.KeyEnter] = newKeyAction("Goto", v.gotoCmd, true)
v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.resetCmd, false)
v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd, false)
v.actions[KeyShiftR] = newKeyAction("Sort Resources", v.sortResourceCmd, true)
v.actions[KeyShiftO] = newKeyAction("Sort Groups", v.sortGroupCmd, true)
}
func (v *aliasView) getTitle() string {
return aliasTitle
}

View File

@ -20,6 +20,7 @@ type (
igniter interface {
tview.Primitive
getTitle() string
init(ctx context.Context, ns string)
}
@ -30,6 +31,7 @@ type (
resourceViewer interface {
igniter
setEnterFn(enterFn)
}
appView struct {
@ -90,6 +92,8 @@ func NewApp(cfg *config.Config) *appView {
v.actions[tcell.KeyDelete] = newKeyAction("Erase", v.eraseCmd, false)
v.actions[tcell.KeyTab] = newKeyAction("Focus", v.focusCmd, false)
// v.actions[KeyO] = newKeyAction("RBAC", v.rbacCmd, false)
return &v
}
@ -156,6 +160,11 @@ func (a *appView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
func (a *appView) rbacCmd(evt *tcell.EventKey) *tcell.EventKey {
a.inject(newRBACView(a, "", "aa_k9s", clusterRole))
return evt
}
func (a *appView) redrawCmd(evt *tcell.EventKey) *tcell.EventKey {
a.Draw()
return evt

View File

@ -33,6 +33,13 @@ func aliasColorer(string, *resource.RowEvent) tcell.Color {
return tcell.ColorFuchsia
}
func rbacColorer(ns string, r *resource.RowEvent) tcell.Color {
c := defaultColorer(ns, r)
// return tcell.ColorDarkOliveGreen
return c
}
func podColorer(ns string, r *resource.RowEvent) tcell.Color {
c := defaultColorer(ns, r)
@ -62,6 +69,7 @@ func podColorer(ns string, r *resource.RowEvent) tcell.Color {
c = errColor
}
}
return c
}
@ -74,6 +82,7 @@ func ctxColorer(ns string, r *resource.RowEvent) tcell.Color {
if strings.Contains(strings.TrimSpace(r.Fields[0]), "*") {
c = highlightColor
}
return c
}
@ -86,6 +95,7 @@ func pvColorer(ns string, r *resource.RowEvent) tcell.Color {
if strings.TrimSpace(r.Fields[4]) != "Bound" {
return errColor
}
return stdColor
}
@ -103,6 +113,7 @@ func pvcColorer(ns string, r *resource.RowEvent) tcell.Color {
if strings.TrimSpace(r.Fields[markCol]) != "Bound" {
c = errColor
}
return c
}
@ -119,6 +130,7 @@ func pdbColorer(ns string, r *resource.RowEvent) tcell.Color {
if strings.TrimSpace(r.Fields[markCol]) != strings.TrimSpace(r.Fields[markCol+1]) {
return errColor
}
return stdColor
}
@ -135,6 +147,7 @@ func dpColorer(ns string, r *resource.RowEvent) tcell.Color {
if strings.TrimSpace(r.Fields[markCol]) != strings.TrimSpace(r.Fields[markCol+1]) {
return errColor
}
return stdColor
}
@ -151,6 +164,7 @@ func stsColorer(ns string, r *resource.RowEvent) tcell.Color {
if strings.TrimSpace(r.Fields[markCol]) != strings.TrimSpace(r.Fields[markCol+1]) {
return errColor
}
return stdColor
}
@ -167,6 +181,7 @@ func rsColorer(ns string, r *resource.RowEvent) tcell.Color {
if strings.TrimSpace(r.Fields[markCol]) != strings.TrimSpace(r.Fields[markCol+1]) {
return errColor
}
return stdColor
}
@ -184,6 +199,7 @@ func evColorer(ns string, r *resource.RowEvent) tcell.Color {
case "Killing":
c = killColor
}
return c
}

View File

@ -44,7 +44,7 @@ func (c *command) run(cmd string) bool {
}
}()
var v igniter
var v resourceViewer
switch cmd {
case "q", "quit":
c.app.Stop()
@ -56,12 +56,19 @@ func (c *command) run(cmd string) bool {
if res, ok := resourceViews()[cmd]; ok {
var r resource.List
if res.listMxFn != nil {
r = res.listMxFn(c.app.conn(), k8s.NewMetricsServer(c.app.conn()), resource.DefaultNamespace)
r = res.listMxFn(c.app.conn(),
k8s.NewMetricsServer(c.app.conn()),
resource.DefaultNamespace,
)
} else {
r = res.listFn(c.app.conn(), resource.DefaultNamespace)
}
v = res.viewFn(res.title, c.app, r, res.colorerFn)
c.app.flash(flashInfo, fmt.Sprintf("Viewing %s in namespace %s...", res.title, c.app.config.ActiveNamespace()))
if res.enterFn != nil {
v.setEnterFn(res.enterFn)
}
const fmat = "Viewing %s in namespace %s..."
c.app.flash(flashInfo, fmt.Sprintf(fmat, res.title, c.app.config.ActiveNamespace()))
log.Debug().Msgf("Running command %s", cmd)
c.exec(cmd, v)
return true

View File

@ -75,7 +75,7 @@ func (v *helpView) init(_ context.Context, _ string) {
}
fmt.Fprintf(v, "🏠 [aqua::b]%s\n", "General")
for _, h := range general {
fmt.Fprintf(v, "[pink::b]%9s [gray::]%s\n", h.key, h.description)
v.printHelp(h.key, h.description)
}
navigation := []helpItem{
@ -90,7 +90,7 @@ func (v *helpView) init(_ context.Context, _ string) {
}
fmt.Fprintf(v, "\n🤖 [aqua::b]%s\n", "View Navigation")
for _, h := range navigation {
fmt.Fprintf(v, "[pink::b]%9s [gray::]%s\n", h.key, h.description)
v.printHelp(h.key, h.description)
}
views := []helpItem{
@ -99,12 +99,15 @@ func (v *helpView) init(_ context.Context, _ string) {
}
fmt.Fprintf(v, "\n😱 [aqua::b]%s\n", "Help")
for _, h := range views {
fmt.Fprintf(v, "[pink::b]%9s [gray::]%s\n", h.key, h.description)
v.printHelp(h.key, h.description)
}
v.app.setHints(v.hints())
}
func (v *helpView) printHelp(key, desc string) {
fmt.Fprintf(v, "[pink::b]%9s [white::]%s\n", key, desc)
}
func (v *helpView) hints() hints {
return v.actions.toHints()
}

View File

@ -46,6 +46,7 @@ func deltas(c, n string) string {
if len(c) == 0 {
return n
}
switch strings.Compare(c, n) {
case 1, -1:
return delta(n)
@ -76,6 +77,7 @@ func numerical(s string) (int, bool) {
func delta(s string) string {
return suffix(s, "𝜟")
}
func plus(s string) string {
return suffix(s, "+")
}

363
internal/views/rbac.go Normal file
View File

@ -0,0 +1,363 @@
package views
import (
"context"
"fmt"
"reflect"
"strings"
"time"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
)
const (
clusterRole roleKind = iota
role
all = "*"
rbacTitle = "RBAC"
rbacTitleFmt = " [aqua::b]%s([fuchsia::b]%s[aqua::-])"
)
type (
roleKind = int8
rbacView struct {
*tableView
current igniter
cancel context.CancelFunc
roleType roleKind
roleName string
cache resource.RowEvents
}
)
var (
rbacHeaderVerbs = resource.Row{
"GET ",
"LIST ",
"DLIST ",
"WATCH ",
"CREATE",
"PATCH ",
"UPDATE",
"DELETE",
"EXTRAS",
}
rbacHeader = append(resource.Row{"NAME", "GROUP"}, rbacHeaderVerbs...)
k8sVerbs = []string{
"get",
"list",
"deletecollection",
"watch",
"create",
"patch",
"update",
"delete",
}
httpVerbs = []string{
"get",
"post",
"put",
"patch",
"delete",
"options",
}
httpTok8sVerbs = map[string]string{
"post": "create",
"put": "update",
}
)
func newRBACView(app *appView, ns, name string, kind roleKind) *rbacView {
v := rbacView{}
{
v.roleName, v.roleType = name, kind
v.tableView = newTableView(app, v.getTitle())
v.currentNS = ns
// v.SetSelectedStyle(tcell.ColorWhite, tcell.ColorDarkSeaGreen, tcell.AttrNone)
v.colorerFn = rbacColorer
v.current = app.content.GetPrimitive("main").(igniter)
}
v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.resetCmd, false)
v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd, false)
v.actions[KeyShiftO] = newKeyAction("Sort Groups", v.sortColCmd(1), true)
ctx, cancel := context.WithCancel(context.Background())
v.cancel = cancel
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
log.Debug().Msg("RBAC Watch bailing out!")
return
case <-time.After(time.Duration(app.config.K9s.RefreshRate) * time.Second):
v.refresh()
v.app.Draw()
}
}
}(ctx)
return &v
}
// Init the view.
func (v *rbacView) init(_ context.Context, ns string) {
// v.baseTitle = v.getTitle()
v.sortCol = sortColumn{1, len(rbacHeader), true}
v.refresh()
v.app.SetFocus(v)
}
func (v *rbacView) getTitle() string {
title := "ClusterRole"
if v.roleType == role {
title = "Role"
}
return fmt.Sprintf(rbacTitleFmt, title, v.roleName)
}
func (v *rbacView) refresh() {
data, err := v.reconcile(v.currentNS, v.roleName, v.roleType)
if err != nil {
log.Error().Err(err).Msgf("Unable to reconcile for %s:%d", v.roleName, v.roleType)
}
v.update(data)
}
func (v *rbacView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.cmdBuff.empty() {
v.cmdBuff.reset()
return nil
}
return v.backCmd(evt)
}
func (v *rbacView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
if v.cancel != nil {
v.cancel()
}
if v.cmdBuff.isActive() {
v.cmdBuff.reset()
} else {
v.app.inject(v.current)
}
return nil
}
func (v *rbacView) hints() hints {
return v.actions.toHints()
}
func (v *rbacView) reconcile(ns, name string, kind roleKind) (resource.TableData, error) {
evts, err := v.rowEvents(ns, name, kind)
if err != nil {
return resource.TableData{}, err
}
data := resource.TableData{
Header: rbacHeader,
Rows: make(resource.RowEvents, len(evts)),
Namespace: resource.NotNamespaced,
}
noDeltas := make(resource.Row, len(rbacHeader))
if len(v.cache) == 0 {
for k, ev := range evts {
ev.Action = resource.New
ev.Deltas = noDeltas
data.Rows[k] = ev
}
v.cache = evts
return data, nil
}
for k, ev := range evts {
data.Rows[k] = ev
newr := ev.Fields
if _, ok := v.cache[k]; !ok {
ev.Action, ev.Deltas = watch.Added, noDeltas
continue
}
oldr := v.cache[k].Fields
deltas := make(resource.Row, len(newr))
if !reflect.DeepEqual(oldr, newr) {
ev.Action = watch.Modified
for i, field := range oldr {
if field != newr[i] {
deltas[i] = field
}
}
ev.Deltas = deltas
} else {
ev.Action = resource.Unchanged
ev.Deltas = noDeltas
}
}
v.cache = evts
for k := range v.cache {
if _, ok := data.Rows[k]; !ok {
delete(v.cache, k)
}
}
return data, nil
}
func (v *rbacView) rowEvents(ns, name string, kind roleKind) (resource.RowEvents, error) {
var (
evts resource.RowEvents
err error
)
switch kind {
case clusterRole:
evts, err = v.clusterPolicies(name)
case role:
evts, err = v.namespacedPolicies(name)
default:
return evts, fmt.Errorf("Expecting clusterrole/role but found %d", kind)
}
if err != nil {
log.Error().Err(err).Msg("Unable to load CR")
return evts, err
}
return evts, nil
}
func toGroup(g string) string {
if g == "" {
return "v1"
}
return g
}
func (v *rbacView) clusterPolicies(name string) (resource.RowEvents, error) {
cr, err := v.app.conn().DialOrDie().Rbac().ClusterRoles().Get(name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return v.parseRules(cr.Rules), nil
}
func (v *rbacView) namespacedPolicies(path string) (resource.RowEvents, error) {
ns, na := namespaced(path)
cr, err := v.app.conn().DialOrDie().Rbac().Roles(ns).Get(na, metav1.GetOptions{})
if err != nil {
return nil, err
}
return v.parseRules(cr.Rules), nil
}
func (*rbacView) parseRules(rules []rbacv1.PolicyRule) resource.RowEvents {
const (
nameLen = 60
groupLen = 30
)
m := make(resource.RowEvents, len(rules))
for _, r := range rules {
for _, grp := range r.APIGroups {
for _, res := range r.Resources {
k := res
if grp != "" {
k = res + "." + grp
}
for _, na := range r.ResourceNames {
n := k + "/" + na
m[n] = &resource.RowEvent{
Fields: makeRow(resource.Pad(n, nameLen), resource.Pad(toGroup(grp), groupLen), asVerbs(r.Verbs...)),
}
}
m[k] = &resource.RowEvent{
Fields: makeRow(resource.Pad(k, nameLen), resource.Pad(toGroup(grp), groupLen), asVerbs(r.Verbs...)),
}
}
}
for _, nres := range r.NonResourceURLs {
if nres[0] != '/' {
nres = "/" + nres
}
m[nres] = &resource.RowEvent{
Fields: makeRow(resource.Pad(nres, nameLen), resource.Pad(resource.NAValue, groupLen), asVerbs(r.Verbs...)),
}
}
}
return m
}
func makeRow(res, group string, verbs []string) resource.Row {
r := make(resource.Row, 0, 12)
r = append(r, res, group)
return append(r, verbs...)
}
func asVerbs(verbs ...string) resource.Row {
const (
verbLen = 4
unknownLen = 30
)
r := make(resource.Row, 0, len(k8sVerbs)+1)
for _, v := range k8sVerbs {
r = append(r, resource.Pad(toVerbIcon(hasVerb(verbs, v)), 4))
}
var unknowns []string
for _, v := range verbs {
if hv, ok := httpTok8sVerbs[v]; ok {
v = hv
}
if !hasVerb(k8sVerbs, v) && v != all {
unknowns = append(unknowns, v)
}
}
return append(r, resource.Truncate(strings.Join(unknowns, ","), unknownLen))
}
func toVerbIcon(ok bool) string {
if ok {
return "[green::b] ✓ [::]"
}
return "[orangered::b] 𐄂 [::]"
}
func hasVerb(verbs []string, verb string) bool {
if len(verbs) == 1 && verbs[0] == all {
return true
}
for _, v := range verbs {
if hv, ok := httpTok8sVerbs[v]; ok {
if hv == verb {
return true
}
}
if v == verb {
return true
}
}
return false
}

115
internal/views/rbac_test.go Normal file
View File

@ -0,0 +1,115 @@
package views
import (
"testing"
"github.com/derailed/k9s/internal/resource"
"github.com/stretchr/testify/assert"
rbacv1 "k8s.io/api/rbac/v1"
)
func TestHasVerb(t *testing.T) {
uu := []struct {
vv []string
v string
e bool
}{
{[]string{"*"}, "get", true},
{[]string{"get", "list", "watch"}, "watch", true},
{[]string{"get", "dope", "list"}, "watch", false},
{[]string{"get"}, "get", true},
{[]string{"post"}, "create", true},
{[]string{"put"}, "update", true},
{[]string{"list", "deletecollection"}, "deletecollection", true},
}
for _, u := range uu {
assert.Equal(t, u.e, hasVerb(u.vv, u.v))
}
}
func TestAsVerbs(t *testing.T) {
ok, nok := toVerbIcon(true), toVerbIcon(false)
uu := []struct {
vv []string
e resource.Row
}{
{[]string{"*"}, resource.Row{ok, ok, ok, ok, ok, ok, ok, ok, ""}},
{[]string{"get", "list", "patch"}, resource.Row{ok, ok, nok, nok, nok, ok, nok, nok, ""}},
{[]string{"get", "list", "deletecollection", "post"}, resource.Row{ok, ok, ok, nok, ok, nok, nok, nok, ""}},
{[]string{"get", "list", "blee"}, resource.Row{ok, ok, nok, nok, nok, nok, nok, nok, "blee"}},
}
for _, u := range uu {
assert.Equal(t, u.e, asVerbs(u.vv...))
}
}
func TestParseRules(t *testing.T) {
ok, nok := toVerbIcon(true), toVerbIcon(false)
_ = nok
uu := []struct {
pp []rbacv1.PolicyRule
e map[string]resource.Row
}{
{
[]rbacv1.PolicyRule{
{APIGroups: []string{"*"}, Resources: []string{"*"}, Verbs: []string{"*"}},
},
map[string]resource.Row{
"*.*": resource.Row{resource.Pad("*.*", 60), resource.Pad("*", 30), ok, ok, ok, ok, ok, ok, ok, ok, ""},
},
},
{
[]rbacv1.PolicyRule{
{APIGroups: []string{"*"}, Resources: []string{"*"}, Verbs: []string{"get"}},
},
map[string]resource.Row{
"*.*": resource.Row{resource.Pad("*.*", 60), resource.Pad("*", 30), ok, nok, nok, nok, nok, nok, nok, nok, ""},
},
},
{
[]rbacv1.PolicyRule{
{APIGroups: []string{""}, Resources: []string{"*"}, Verbs: []string{"list"}},
},
map[string]resource.Row{
"*": resource.Row{resource.Pad("*", 60), resource.Pad("v1", 30), nok, ok, nok, nok, nok, nok, nok, nok, ""},
},
},
{
[]rbacv1.PolicyRule{
{APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{"list"}, ResourceNames: []string{"fred"}},
},
map[string]resource.Row{
"pods": resource.Row{resource.Pad("pods", 60), resource.Pad("v1", 30), nok, ok, nok, nok, nok, nok, nok, nok, ""},
"pods/fred": resource.Row{resource.Pad("pods/fred", 60), resource.Pad("v1", 30), nok, ok, nok, nok, nok, nok, nok, nok, ""},
},
},
{
[]rbacv1.PolicyRule{
{APIGroups: []string{}, Resources: []string{}, Verbs: []string{"get"}, NonResourceURLs: []string{"/fred"}},
},
map[string]resource.Row{
"/fred": resource.Row{resource.Pad("/fred", 60), resource.Pad("<n/a>", 30), ok, nok, nok, nok, nok, nok, nok, nok, ""},
},
},
{
[]rbacv1.PolicyRule{
{APIGroups: []string{}, Resources: []string{}, Verbs: []string{"get"}, NonResourceURLs: []string{"fred"}},
},
map[string]resource.Row{
"/fred": resource.Row{resource.Pad("/fred", 60), resource.Pad("<n/a>", 30), ok, nok, nok, nok, nok, nok, nok, nok, ""},
},
},
}
var v rbacView
for _, u := range uu {
evts := v.parseRules(u.pp)
for k, v := range u.e {
assert.Equal(t, v, evts[k].Fields)
}
}
}

View File

@ -4,6 +4,7 @@ import (
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
)
type (
@ -11,6 +12,7 @@ type (
listFn func(c resource.Connection, ns string) resource.List
listMxFn func(c resource.Connection, mx resource.MetricsServer, ns string) resource.List
colorerFn func(ns string, evt *resource.RowEvent) tcell.Color
enterFn func(app *appView, ns, resource, selection string)
resCmd struct {
title string
@ -18,6 +20,7 @@ type (
viewFn viewFn
listFn listFn
listMxFn listMxFn
enterFn enterFn
colorerFn colorerFn
}
)
@ -72,6 +75,16 @@ func allCRDs(c k8s.Connection) map[string]k8s.APIGroup {
return m
}
func showRBAC(app *appView, ns, resource, selection string) {
log.Debug().Msgf("Entered FN on `%s`--%s:%s", ns, resource, selection)
kind := clusterRole
if resource == "role" {
kind = role
}
app.command.pushCmd("policies")
app.inject(newRBACView(app, ns, selection, kind))
}
func resourceViews() map[string]resCmd {
return map[string]resCmd{
"cm": {
@ -86,6 +99,7 @@ func resourceViews() map[string]resCmd {
api: "rbac.authorization.k8s.io",
viewFn: newResourceView,
listFn: resource.NewClusterRoleList,
enterFn: showRBAC,
colorerFn: defaultColorer,
},
"crb": {
@ -226,6 +240,7 @@ func resourceViews() map[string]resCmd {
api: "rbac.authorization.k8s.io",
viewFn: newResourceView,
listFn: resource.NewRoleList,
enterFn: showRBAC,
colorerFn: defaultColorer,
},
"rs": {

View File

@ -42,6 +42,7 @@ type (
selectedNS string
update sync.Mutex
list resource.List
enterFn enterFn
extraActionsFn func(keyActions)
selectedFn func() string
decorateDataFn func(resource.TableData) resource.TableData
@ -124,9 +125,21 @@ func (v *resourceView) hints() hints {
return v.CurrentPage().Item.(hinter).hints()
}
func (v *resourceView) setEnterFn(f enterFn) {
v.enterFn = f
}
// ----------------------------------------------------------------------------
// Actions...
func (v *resourceView) enterCmd(*tcell.EventKey) *tcell.EventKey {
v.app.flash(flashInfo, "Enter pressed...")
if v.enterFn != nil {
v.enterFn(v.app, v.list.GetNamespace(), v.list.GetName(), v.selectedItem)
}
return nil
}
func (v *resourceView) refreshCmd(*tcell.EventKey) *tcell.EventKey {
v.app.flash(flashInfo, "Refreshing...")
v.refresh()
@ -359,6 +372,8 @@ func (v *resourceView) refreshActions() {
}
}
aa[tcell.KeyEnter] = newKeyAction("Enter", v.enterCmd, true)
aa[tcell.KeyCtrlR] = newKeyAction("Refresh", v.refreshCmd, false)
aa[KeyHelp] = newKeyAction("Help", v.app.noopCmd, false)
aa[KeyP] = newKeyAction("Previous", v.app.prevCmd, false)
@ -370,7 +385,7 @@ func (v *resourceView) refreshActions() {
aa[tcell.KeyCtrlD] = newKeyAction("Delete", v.deleteCmd, true)
}
if v.list.Access(resource.ViewAccess) {
aa[KeyV] = newKeyAction("View", v.viewCmd, true)
aa[KeyY] = newKeyAction("YAML", v.viewCmd, true)
}
if v.list.Access(resource.DescribeAccess) {
aa[KeyD] = newKeyAction("Describe", v.describeCmd, true)

View File

@ -14,7 +14,7 @@ import (
)
const (
titleFmt = " [aqua::b]%s[aqua::-]([fuchsia::b]%d[aqua::-]) "
titleFmt = " [aqua::b]%s[aqua::-][[fuchsia::b]%d[aqua::-]] "
searchFmt = "<[green::b]/%s[aqua::]> "
nsTitleFmt = " [aqua::b]%s([fuchsia::b]%s[aqua::-])[aqua::-][[aqua::b]%d[aqua::-]][aqua::-] "
)
@ -63,24 +63,8 @@ func newTableView(app *appView, title string) *tableView {
v.SetSelectable(true, false)
v.SetSelectedStyle(tcell.ColorBlack, tcell.ColorAqua, tcell.AttrBold)
v.SetInputCapture(v.keyboard)
v.registerHandlers()
}
v.actions[KeyShiftI] = newKeyAction("Invert", v.sortInvertCmd, true)
v.actions[KeyShiftN] = newKeyAction("Sort Name", v.sortColCmd(0), true)
v.actions[KeyShiftA] = newKeyAction("Sort Age", v.sortColCmd(-1), true)
v.actions[KeySlash] = newKeyAction("Filter Mode", v.activateCmd, false)
v.actions[tcell.KeyEscape] = newKeyAction("Filter Reset", v.resetCmd, false)
v.actions[tcell.KeyEnter] = newKeyAction("Filter", v.filterCmd, false)
v.actions[tcell.KeyBackspace2] = newKeyAction("Erase", v.eraseCmd, false)
v.actions[tcell.KeyBackspace] = newKeyAction("Erase", v.eraseCmd, false)
v.actions[tcell.KeyDelete] = newKeyAction("Erase", v.eraseCmd, false)
v.actions[KeyG] = newKeyAction("Top", app.puntCmd, false)
v.actions[KeyShiftG] = newKeyAction("Bottom", app.puntCmd, false)
v.actions[KeyB] = newKeyAction("Down", v.pageDownCmd, false)
v.actions[KeyF] = newKeyAction("Up", v.pageUpCmd, false)
return &v
}
@ -128,6 +112,7 @@ func (v *tableView) pageDownCmd(evt *tcell.EventKey) *tcell.EventKey {
func (v *tableView) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
v.cmdBuff.setActive(false)
v.refresh()
return nil
}
@ -309,10 +294,11 @@ func (v *tableView) doUpdate(data resource.TableData) {
row++
// for k := range data.Rows {
// log.Debug().Msgf("Keys: %s", k)
// log.Debug().Msgf("Keys: `%s`", k)
// }
keys := v.sortRows(data)
// log.Debug().Msgf("KEYS %#v", keys)
groupKeys := map[string][]string{}
for _, k := range keys {
// log.Debug().Msgf("RKEY: %s", k)
@ -447,6 +433,24 @@ func (v *tableView) resetTitle() {
// ----------------------------------------------------------------------------
// Event listeners...
func (v *tableView) registerHandlers() {
v.actions[KeyShiftI] = newKeyAction("Invert", v.sortInvertCmd, true)
v.actions[KeyShiftN] = newKeyAction("Sort Name", v.sortColCmd(0), true)
v.actions[KeyShiftA] = newKeyAction("Sort Age", v.sortColCmd(-1), true)
v.actions[KeySlash] = newKeyAction("Filter Mode", v.activateCmd, false)
v.actions[tcell.KeyEscape] = newKeyAction("Filter Reset", v.resetCmd, false)
v.actions[tcell.KeyEnter] = newKeyAction("Filter", v.filterCmd, false)
v.actions[tcell.KeyBackspace2] = newKeyAction("Erase", v.eraseCmd, false)
v.actions[tcell.KeyBackspace] = newKeyAction("Erase", v.eraseCmd, false)
v.actions[tcell.KeyDelete] = newKeyAction("Erase", v.eraseCmd, false)
v.actions[KeyG] = newKeyAction("Top", v.app.puntCmd, false)
v.actions[KeyShiftG] = newKeyAction("Bottom", v.app.puntCmd, false)
v.actions[KeyB] = newKeyAction("Down", v.pageDownCmd, false)
v.actions[KeyF] = newKeyAction("Up", v.pageUpCmd, false)
}
func (v *tableView) changed(s string) {
}