preliminary work on sort by columns
parent
fbb885d2f7
commit
16cf464c7a
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Release v0.2.6
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Thank you to all that contributed with flushing out issues with K9s! I'll try
|
||||||
|
to mark some of these issues as fixed. But if you don't mind grab the latest
|
||||||
|
rev and see if we're happier with some of the fixes!
|
||||||
|
|
||||||
|
If you've filed an issue please help me verify and close.
|
||||||
|
|
||||||
|
Thank you so much for your support!!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Change Logs
|
||||||
|
|
||||||
|
1. Preliminary drop on sorting by resource columns
|
||||||
|
2. Add sort by namespace, name and age for all views
|
||||||
|
3. Add invert sort functionality on all sortable views
|
||||||
|
4. Add sort on pod views for metrics and most other columns
|
||||||
|
5. For all other views we will add custom sort on a per request basis
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resolved Bugs
|
||||||
|
|
||||||
|
+ [Issue #117](https://github.com/derailed/k9s/issues/117)
|
||||||
|
Was filtering out inactive ns which need to be there for all to see anyway!
|
||||||
|
|
@ -30,7 +30,9 @@ func InList(ll []string, n string) bool {
|
||||||
func InNSList(nn []interface{}, ns string) bool {
|
func InNSList(nn []interface{}, ns string) bool {
|
||||||
ss := make([]string, len(nn))
|
ss := make([]string, len(nn))
|
||||||
for i, n := range nn {
|
for i, n := range nn {
|
||||||
ss[i] = n.(v1.Namespace).Name
|
if nsp, ok := n.(v1.Namespace); ok {
|
||||||
|
ss[i] = nsp.Name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return InList(ss, ns)
|
return InList(ss, ns)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,9 @@ func (c *Config) NamespaceNames() ([]string, error) {
|
||||||
}
|
}
|
||||||
nn := make([]string, 0, len(ll))
|
nn := make([]string, 0, len(ll))
|
||||||
for _, n := range ll {
|
for _, n := range ll {
|
||||||
nn = append(nn, n.(v1.Namespace).Name)
|
if ns, ok := n.(v1.Namespace); ok {
|
||||||
|
nn = append(nn, ns.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nn, nil
|
return nn, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package k8s
|
package k8s
|
||||||
|
|
||||||
import (
|
import (
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -31,10 +30,8 @@ func (*Namespace) List(_ string) (Collection, error) {
|
||||||
|
|
||||||
cc := make(Collection, len(rr.Items))
|
cc := make(Collection, len(rr.Items))
|
||||||
for i, r := range rr.Items {
|
for i, r := range rr.Items {
|
||||||
if r.Status.Phase == v1.NamespaceActive {
|
|
||||||
cc[i] = r
|
cc[i] = r
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return cc, nil
|
return cc, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,16 +89,6 @@ func TestCMMarshal(t *testing.T) {
|
||||||
assert.Equal(t, cmYaml(), ma)
|
assert.Equal(t, cmYaml(), ma)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCMListSort(t *testing.T) {
|
|
||||||
setup(t)
|
|
||||||
|
|
||||||
ca := NewMockCaller()
|
|
||||||
l := resource.NewConfigMapListWithArgs("blee", resource.NewConfigMapWithArgs(ca))
|
|
||||||
kk := []string{"c", "b", "a"}
|
|
||||||
l.SortFn()(kk)
|
|
||||||
assert.Equal(t, []string{"a", "b", "c"}, kk)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCMListHasName(t *testing.T) {
|
func TestCMListHasName(t *testing.T) {
|
||||||
setup(t)
|
setup(t)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,16 +52,6 @@ func TestCTXDelete(t *testing.T) {
|
||||||
ca.VerifyWasCalledOnce().Delete("", "fred")
|
ca.VerifyWasCalledOnce().Delete("", "fred")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCTXListSort(t *testing.T) {
|
|
||||||
setup(t)
|
|
||||||
|
|
||||||
ca := NewMockSwitchableRes()
|
|
||||||
l := resource.NewContextListWithArgs("blee", resource.NewContextWithArgs(ca))
|
|
||||||
kk := []string{"c", "b", "a"}
|
|
||||||
l.SortFn()(kk)
|
|
||||||
assert.Equal(t, []string{"a", "b", "c"}, kk)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCTXListHasName(t *testing.T) {
|
func TestCTXListHasName(t *testing.T) {
|
||||||
setup(t)
|
setup(t)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package resource
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
|
|
@ -34,9 +33,6 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// SortFn provides for sorting items in list.
|
|
||||||
SortFn func([]string)
|
|
||||||
|
|
||||||
// RowEvent represents a call for action after a resource reconciliation.
|
// RowEvent represents a call for action after a resource reconciliation.
|
||||||
// Tracks whether a resource got added, deleted or updated.
|
// Tracks whether a resource got added, deleted or updated.
|
||||||
RowEvent struct {
|
RowEvent struct {
|
||||||
|
|
@ -71,7 +67,6 @@ type (
|
||||||
GetName() string
|
GetName() string
|
||||||
Access(flag int) bool
|
Access(flag int) bool
|
||||||
HasXRay() bool
|
HasXRay() bool
|
||||||
SortFn() SortFn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Columnar tracks resources that can be diplayed in a tabular fashion.
|
// Columnar tracks resources that can be diplayed in a tabular fashion.
|
||||||
|
|
@ -85,6 +80,9 @@ type (
|
||||||
// Row represents a collection of string fields.
|
// Row represents a collection of string fields.
|
||||||
Row []string
|
Row []string
|
||||||
|
|
||||||
|
// Rows represents a collection of rows.
|
||||||
|
Rows []Row
|
||||||
|
|
||||||
// Columnars a collection of columnars.
|
// Columnars a collection of columnars.
|
||||||
Columnars []Columnar
|
Columnars []Columnar
|
||||||
|
|
||||||
|
|
@ -112,7 +110,6 @@ type (
|
||||||
xray bool
|
xray bool
|
||||||
api Resource
|
api Resource
|
||||||
cache RowEvents
|
cache RowEvents
|
||||||
sortFn func([]string)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -130,13 +127,6 @@ func newList(ns, name string, api Resource, v int) *list {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *list) SortFn() SortFn {
|
|
||||||
if l.sortFn == nil {
|
|
||||||
return sort.Strings
|
|
||||||
}
|
|
||||||
return l.sortFn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *list) HasXRay() bool {
|
func (l *list) HasXRay() bool {
|
||||||
return l.xray
|
return l.xray
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,16 +90,6 @@ func TestSecretMarshal(t *testing.T) {
|
||||||
assert.Equal(t, secretYaml(), ma)
|
assert.Equal(t, secretYaml(), ma)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSecretListSort(t *testing.T) {
|
|
||||||
setup(t)
|
|
||||||
|
|
||||||
ca := NewMockCaller()
|
|
||||||
l := resource.NewSecretListWithArgs("blee", resource.NewSecretWithArgs(ca))
|
|
||||||
kk := []string{"c", "b", "a"}
|
|
||||||
l.SortFn()(kk)
|
|
||||||
assert.Equal(t, []string{"a", "b", "c"}, kk)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSecretListHasName(t *testing.T) {
|
func TestSecretListHasName(t *testing.T) {
|
||||||
setup(t)
|
setup(t)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package views
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -25,17 +24,18 @@ type aliasView struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAliasView(app *appView) *aliasView {
|
func newAliasView(app *appView) *aliasView {
|
||||||
v := aliasView{tableView: newTableView(app, aliasTitle, nil)}
|
v := aliasView{tableView: newTableView(app, aliasTitle)}
|
||||||
{
|
{
|
||||||
v.SetSelectedStyle(tcell.ColorWhite, tcell.ColorFuchsia, tcell.AttrNone)
|
v.SetSelectedStyle(tcell.ColorWhite, tcell.ColorFuchsia, tcell.AttrNone)
|
||||||
v.colorerFn = aliasColorer
|
v.colorerFn = aliasColorer
|
||||||
v.current = app.content.GetPrimitive("main").(igniter)
|
v.current = app.content.GetPrimitive("main").(igniter)
|
||||||
v.sortFn = v.sorterFn
|
|
||||||
v.currentNS = ""
|
v.currentNS = ""
|
||||||
}
|
}
|
||||||
v.actions[tcell.KeyEnter] = newKeyAction("Goto", v.gotoCmd, true)
|
v.actions[tcell.KeyEnter] = newKeyAction("Goto", v.gotoCmd, true)
|
||||||
v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.resetCmd, false)
|
v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.resetCmd, false)
|
||||||
v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd, false)
|
v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd, false)
|
||||||
|
v.actions[KeyShiftR] = newKeyAction("Sort Resources", v.sortResourceCmd, true)
|
||||||
|
v.actions[KeyShiftG] = newKeyAction("Sort Groups", v.sortGroupCmd, true)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
v.cancel = cancel
|
v.cancel = cancel
|
||||||
|
|
@ -55,10 +55,6 @@ func newAliasView(app *appView) *aliasView {
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *aliasView) sorterFn(ss []string) {
|
|
||||||
sort.Strings(ss)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init the view.
|
// Init the view.
|
||||||
func (v *aliasView) init(context.Context, string) {
|
func (v *aliasView) init(context.Context, string) {
|
||||||
v.update(v.hydrate())
|
v.update(v.hydrate())
|
||||||
|
|
@ -70,6 +66,18 @@ func (v *aliasView) getTitle() string {
|
||||||
return aliasTitle
|
return aliasTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *aliasView) sortResourceCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
v.sortCol.index, v.sortCol.asc = 1, true
|
||||||
|
v.refresh()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *aliasView) sortGroupCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
v.sortCol.index, v.sortCol.asc = 2, true
|
||||||
|
v.refresh()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (v *aliasView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
func (v *aliasView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if !v.cmdBuff.empty() {
|
if !v.cmdBuff.empty() {
|
||||||
v.cmdBuff.reset()
|
v.cmdBuff.reset()
|
||||||
|
|
@ -123,9 +131,9 @@ func (v *aliasView) hydrate() resource.TableData {
|
||||||
cmds := helpCmds()
|
cmds := helpCmds()
|
||||||
|
|
||||||
data := resource.TableData{
|
data := resource.TableData{
|
||||||
Header: resource.Row{"ALIAS", "RESOURCE", "APIGROUP"},
|
Header: resource.Row{"NAME", "RESOURCE", "APIGROUP"},
|
||||||
Rows: make(resource.RowEvents, len(cmds)),
|
Rows: make(resource.RowEvents, len(cmds)),
|
||||||
Namespace: "",
|
Namespace: resource.NotNamespaced,
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range cmds {
|
for k := range cmds {
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ func (a *appView) Init(v string, rate int, flags *genericclioptions.ConfigFlags)
|
||||||
header := tview.NewFlex()
|
header := tview.NewFlex()
|
||||||
{
|
{
|
||||||
header.SetDirection(tview.FlexColumn)
|
header.SetDirection(tview.FlexColumn)
|
||||||
header.AddItem(a.clusterInfoView, 55, 1, false)
|
header.AddItem(a.clusterInfoView, 35, 1, false)
|
||||||
header.AddItem(a.menuView, 0, 1, false)
|
header.AddItem(a.menuView, 0, 1, false)
|
||||||
header.AddItem(logoView(), 26, 1, false)
|
header.AddItem(logoView(), 26, 1, false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,12 @@ func (v *clusterInfoView) init() {
|
||||||
v.SetCell(row, 1, v.infoCell(cluster.UserName()))
|
v.SetCell(row, 1, v.infoCell(cluster.UserName()))
|
||||||
row++
|
row++
|
||||||
|
|
||||||
v.SetCell(row, 0, v.sectionCell("K9s Version"))
|
v.SetCell(row, 0, v.sectionCell("K9s Rev"))
|
||||||
v.SetCell(row, 1, v.infoCell(v.app.version))
|
v.SetCell(row, 1, v.infoCell(v.app.version))
|
||||||
row++
|
row++
|
||||||
|
|
||||||
rev := cluster.Version()
|
rev := cluster.Version()
|
||||||
v.SetCell(row, 0, v.sectionCell("K8s Version"))
|
v.SetCell(row, 0, v.sectionCell("K8s Rev"))
|
||||||
v.SetCell(row, 1, v.infoCell(rev))
|
v.SetCell(row, 1, v.infoCell(rev))
|
||||||
row++
|
row++
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ func newContextView(t string, app *appView, list resource.List, c colorerFn) res
|
||||||
v := contextView{newResourceView(t, app, list, c).(*resourceView)}
|
v := contextView{newResourceView(t, app, list, c).(*resourceView)}
|
||||||
{
|
{
|
||||||
v.extraActionsFn = v.extraActions
|
v.extraActionsFn = v.extraActions
|
||||||
|
v.getTV().cleanseFn = v.cleanser
|
||||||
v.switchPage("ctx")
|
v.switchPage("ctx")
|
||||||
}
|
}
|
||||||
return &v
|
return &v
|
||||||
|
|
@ -39,15 +40,19 @@ func (v *contextView) useCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *contextView) useContext(name string) error {
|
func (*contextView) cleanser(s string) string {
|
||||||
ctx := strings.TrimSpace(name)
|
name := strings.TrimSpace(s)
|
||||||
if strings.HasSuffix(ctx, "*") {
|
if strings.HasSuffix(name, "*") {
|
||||||
ctx = strings.TrimRight(ctx, "*")
|
name = strings.TrimRight(name, "*")
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(ctx, "(𝜟)") {
|
if strings.HasSuffix(name, "(𝜟)") {
|
||||||
ctx = strings.TrimRight(ctx, "(𝜟)")
|
name = strings.TrimRight(name, "(𝜟)")
|
||||||
}
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *contextView) useContext(name string) error {
|
||||||
|
ctx := v.cleanser(name)
|
||||||
if err := v.list.Resource().(*resource.Context).Switch(ctx); err != nil {
|
if err := v.list.Resource().(*resource.Context).Switch(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -59,5 +64,6 @@ func (v *contextView) useContext(name string) error {
|
||||||
if tv, ok := v.GetPrimitive("ctx").(*tableView); ok {
|
if tv, ok := v.GetPrimitive("ctx").(*tableView); ok {
|
||||||
tv.Select(0, 0)
|
tv.Select(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,7 @@ func (v *helpView) init(_ context.Context, _ string) {
|
||||||
{"k", "Up"},
|
{"k", "Up"},
|
||||||
{"j", "Down"},
|
{"j", "Down"},
|
||||||
}
|
}
|
||||||
fmt.Fprintln(v)
|
fmt.Fprintf(v, "\n🤖 [aqua::b]%s\n", "View Navigation")
|
||||||
fmt.Fprintf(v, "🖲 [aqua::b]%s\n", "View Navigation")
|
|
||||||
for _, h := range navigation {
|
for _, h := range navigation {
|
||||||
fmt.Fprintf(v, "[pink::b]%9s [gray::]%s\n", h.key, h.description)
|
fmt.Fprintf(v, "[pink::b]%9s [gray::]%s\n", h.key, h.description)
|
||||||
}
|
}
|
||||||
|
|
@ -98,8 +97,7 @@ func (v *helpView) init(_ context.Context, _ string) {
|
||||||
{"?", "Help"},
|
{"?", "Help"},
|
||||||
{"a", "Aliases view"},
|
{"a", "Aliases view"},
|
||||||
}
|
}
|
||||||
fmt.Fprintln(v)
|
fmt.Fprintf(v, "️️\n😱 [aqua::b]%s\n", "Help")
|
||||||
fmt.Fprintf(v, "️️⁉️ [aqua::b]%s\n", "Help")
|
|
||||||
for _, h := range views {
|
for _, h := range views {
|
||||||
fmt.Fprintf(v, "[pink::b]%9s [gray::]%s\n", h.key, h.description)
|
fmt.Fprintf(v, "[pink::b]%9s [gray::]%s\n", h.key, h.description)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -128,15 +128,15 @@ func (v *menuView) buildMenuTable(hh hints) [][]string {
|
||||||
}
|
}
|
||||||
|
|
||||||
var row, col int
|
var row, col int
|
||||||
firstNS, firstCmd := true, true
|
firstCmd := true
|
||||||
maxKeys := make([]int, colCount+1)
|
maxKeys := make([]int, colCount+1)
|
||||||
for _, h := range hh {
|
for _, h := range hh {
|
||||||
isDigit := menuRX.MatchString(h.mnemonic)
|
isDigit := menuRX.MatchString(h.mnemonic)
|
||||||
if isDigit && firstNS {
|
// if isDigit && firstNS {
|
||||||
row, col, firstNS = 0, col+1, false
|
// row, col, firstNS = 0, 2, false
|
||||||
}
|
// }
|
||||||
if !isDigit && firstCmd {
|
if !isDigit && firstCmd {
|
||||||
row, col, firstCmd = 0, 0, false
|
row, col, firstCmd = 0, col+1, false
|
||||||
}
|
}
|
||||||
if maxKeys[col] < len(h.mnemonic) {
|
if maxKeys[col] < len(h.mnemonic) {
|
||||||
maxKeys[col] = len(h.mnemonic)
|
maxKeys[col] = len(h.mnemonic)
|
||||||
|
|
@ -158,6 +158,7 @@ func (v *menuView) buildMenuTable(hh hints) [][]string {
|
||||||
strTable[row][col] = v.formatMenu(table[row][col], maxKeys[col])
|
strTable[row][col] = v.formatMenu(table[row][col], maxKeys[col])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return strTable
|
return strTable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,6 +166,7 @@ func (*menuView) toMnemonic(s string) string {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
return "<" + strings.ToLower(s) + ">"
|
return "<" + strings.ToLower(s) + ">"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,6 +175,7 @@ func (v *menuView) formatMenu(h hint, size int) string {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fmt.Sprintf(menuIndexFmt, i, resource.Truncate(h.description, 14))
|
return fmt.Sprintf(menuIndexFmt, i, resource.Truncate(h.description, 14))
|
||||||
}
|
}
|
||||||
|
|
||||||
menuFmt := " [dodgerblue::b]%-" + strconv.Itoa(size+2) + "s [white::d]%s "
|
menuFmt := " [dodgerblue::b]%-" + strconv.Itoa(size+2) + "s [white::d]%s "
|
||||||
return fmt.Sprintf(menuFmt, v.toMnemonic(h.mnemonic), h.description)
|
return fmt.Sprintf(menuFmt, v.toMnemonic(h.mnemonic), h.description)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ func newNamespaceView(t string, app *appView, list resource.List, c colorerFn) r
|
||||||
v.extraActionsFn = v.extraActions
|
v.extraActionsFn = v.extraActions
|
||||||
v.selectedFn = v.getSelectedItem
|
v.selectedFn = v.getSelectedItem
|
||||||
v.decorateDataFn = v.decorate
|
v.decorateDataFn = v.decorate
|
||||||
|
v.getTV().cleanseFn = v.cleanser
|
||||||
v.switchPage("ns")
|
v.switchPage("ns")
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,23 @@ func (v *podView) showLogs(path, co string, previous bool) {
|
||||||
func (v *podView) extraActions(aa keyActions) {
|
func (v *podView) extraActions(aa keyActions) {
|
||||||
aa[KeyL] = newKeyAction("Logs", v.logsCmd, true)
|
aa[KeyL] = newKeyAction("Logs", v.logsCmd, true)
|
||||||
aa[KeyS] = newKeyAction("Shell", v.shellCmd, true)
|
aa[KeyS] = newKeyAction("Shell", v.shellCmd, true)
|
||||||
|
aa[KeyShiftR] = newKeyAction("Sort Ready", v.sortColCmd(1, false), true)
|
||||||
|
aa[KeyShiftS] = newKeyAction("Sort Status", v.sortColCmd(2, true), true)
|
||||||
|
aa[KeyShiftT] = newKeyAction("Sort Restart", v.sortColCmd(3, false), true)
|
||||||
|
aa[KeyShiftC] = newKeyAction("Sort CPU", v.sortColCmd(4, false), true)
|
||||||
|
aa[KeyShiftM] = newKeyAction("Sort MEM", v.sortColCmd(5, false), true)
|
||||||
|
aa[KeyShiftO] = newKeyAction("Sort Node", v.sortColCmd(7, true), true)
|
||||||
|
aa[KeyShiftQ] = newKeyAction("Sort QOS", v.sortColCmd(8, true), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *podView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
t := v.getTV()
|
||||||
|
t.sortCol.index, t.sortCol.asc = t.nameColIndex()+col, asc
|
||||||
|
t.refresh()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchContainers(l resource.List, po string, includeInit bool) ([]string, error) {
|
func fetchContainers(l resource.List, po string, includeInit bool) ([]string, error) {
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ func resourceViews() map[string]resCmd {
|
||||||
return map[string]resCmd{
|
return map[string]resCmd{
|
||||||
"cm": {
|
"cm": {
|
||||||
title: "ConfigMaps",
|
title: "ConfigMaps",
|
||||||
api: "core",
|
api: "",
|
||||||
viewFn: newResourceView,
|
viewFn: newResourceView,
|
||||||
listFn: resource.NewConfigMapList,
|
listFn: resource.NewConfigMapList,
|
||||||
colorerFn: defaultColorer,
|
colorerFn: defaultColorer,
|
||||||
|
|
@ -108,14 +108,14 @@ func resourceViews() map[string]resCmd {
|
||||||
},
|
},
|
||||||
"ctx": {
|
"ctx": {
|
||||||
title: "Contexts",
|
title: "Contexts",
|
||||||
api: "core",
|
api: "",
|
||||||
viewFn: newContextView,
|
viewFn: newContextView,
|
||||||
listFn: resource.NewContextList,
|
listFn: resource.NewContextList,
|
||||||
colorerFn: ctxColorer,
|
colorerFn: ctxColorer,
|
||||||
},
|
},
|
||||||
"ds": {
|
"ds": {
|
||||||
title: "DaemonSets",
|
title: "DaemonSets",
|
||||||
api: "core",
|
api: "",
|
||||||
viewFn: newResourceView,
|
viewFn: newResourceView,
|
||||||
listFn: resource.NewDaemonSetList,
|
listFn: resource.NewDaemonSetList,
|
||||||
colorerFn: dpColorer,
|
colorerFn: dpColorer,
|
||||||
|
|
@ -129,14 +129,14 @@ func resourceViews() map[string]resCmd {
|
||||||
},
|
},
|
||||||
"ep": {
|
"ep": {
|
||||||
title: "EndPoints",
|
title: "EndPoints",
|
||||||
api: "core",
|
api: "",
|
||||||
viewFn: newResourceView,
|
viewFn: newResourceView,
|
||||||
listFn: resource.NewEndpointsList,
|
listFn: resource.NewEndpointsList,
|
||||||
colorerFn: defaultColorer,
|
colorerFn: defaultColorer,
|
||||||
},
|
},
|
||||||
"ev": {
|
"ev": {
|
||||||
title: "Events",
|
title: "Events",
|
||||||
api: "core",
|
api: "",
|
||||||
viewFn: newResourceView,
|
viewFn: newResourceView,
|
||||||
listFn: resource.NewEventList,
|
listFn: resource.NewEventList,
|
||||||
colorerFn: evColorer,
|
colorerFn: evColorer,
|
||||||
|
|
@ -164,35 +164,35 @@ func resourceViews() map[string]resCmd {
|
||||||
},
|
},
|
||||||
"no": {
|
"no": {
|
||||||
title: "Nodes",
|
title: "Nodes",
|
||||||
api: "core",
|
api: "",
|
||||||
viewFn: newResourceView,
|
viewFn: newResourceView,
|
||||||
listFn: resource.NewNodeList,
|
listFn: resource.NewNodeList,
|
||||||
colorerFn: nsColorer,
|
colorerFn: nsColorer,
|
||||||
},
|
},
|
||||||
"ns": {
|
"ns": {
|
||||||
title: "Namespaces",
|
title: "Namespaces",
|
||||||
api: "core",
|
api: "",
|
||||||
viewFn: newNamespaceView,
|
viewFn: newNamespaceView,
|
||||||
listFn: resource.NewNamespaceList,
|
listFn: resource.NewNamespaceList,
|
||||||
colorerFn: nsColorer,
|
colorerFn: nsColorer,
|
||||||
},
|
},
|
||||||
"po": {
|
"po": {
|
||||||
title: "Pods",
|
title: "Pods",
|
||||||
api: "core",
|
api: "",
|
||||||
viewFn: newPodView,
|
viewFn: newPodView,
|
||||||
listFn: resource.NewPodList,
|
listFn: resource.NewPodList,
|
||||||
colorerFn: podColorer,
|
colorerFn: podColorer,
|
||||||
},
|
},
|
||||||
"pv": {
|
"pv": {
|
||||||
title: "PersistentVolumes",
|
title: "PersistentVolumes",
|
||||||
api: "core",
|
api: "",
|
||||||
viewFn: newResourceView,
|
viewFn: newResourceView,
|
||||||
listFn: resource.NewPVList,
|
listFn: resource.NewPVList,
|
||||||
colorerFn: pvColorer,
|
colorerFn: pvColorer,
|
||||||
},
|
},
|
||||||
"pvc": {
|
"pvc": {
|
||||||
title: "PersistentVolumeClaims",
|
title: "PersistentVolumeClaims",
|
||||||
api: "core",
|
api: "",
|
||||||
viewFn: newResourceView,
|
viewFn: newResourceView,
|
||||||
listFn: resource.NewPVCList,
|
listFn: resource.NewPVCList,
|
||||||
colorerFn: pvcColorer,
|
colorerFn: pvcColorer,
|
||||||
|
|
@ -206,7 +206,7 @@ func resourceViews() map[string]resCmd {
|
||||||
},
|
},
|
||||||
"rc": {
|
"rc": {
|
||||||
title: "ReplicationControllers",
|
title: "ReplicationControllers",
|
||||||
api: "v1",
|
api: "",
|
||||||
viewFn: newResourceView,
|
viewFn: newResourceView,
|
||||||
listFn: resource.NewReplicationControllerList,
|
listFn: resource.NewReplicationControllerList,
|
||||||
colorerFn: rsColorer,
|
colorerFn: rsColorer,
|
||||||
|
|
@ -227,14 +227,14 @@ func resourceViews() map[string]resCmd {
|
||||||
},
|
},
|
||||||
"sa": {
|
"sa": {
|
||||||
title: "ServiceAccounts",
|
title: "ServiceAccounts",
|
||||||
api: "core",
|
api: "",
|
||||||
viewFn: newResourceView,
|
viewFn: newResourceView,
|
||||||
listFn: resource.NewServiceAccountList,
|
listFn: resource.NewServiceAccountList,
|
||||||
colorerFn: defaultColorer,
|
colorerFn: defaultColorer,
|
||||||
},
|
},
|
||||||
"sec": {
|
"sec": {
|
||||||
title: "Secrets",
|
title: "Secrets",
|
||||||
api: "core",
|
api: "",
|
||||||
viewFn: newResourceView,
|
viewFn: newResourceView,
|
||||||
listFn: resource.NewSecretList,
|
listFn: resource.NewSecretList,
|
||||||
colorerFn: defaultColorer,
|
colorerFn: defaultColorer,
|
||||||
|
|
@ -248,7 +248,7 @@ func resourceViews() map[string]resCmd {
|
||||||
},
|
},
|
||||||
"svc": {
|
"svc": {
|
||||||
title: "Services",
|
title: "Services",
|
||||||
api: "core",
|
api: "",
|
||||||
viewFn: newResourceView,
|
viewFn: newResourceView,
|
||||||
listFn: resource.NewServiceList,
|
listFn: resource.NewServiceList,
|
||||||
colorerFn: defaultColorer,
|
colorerFn: defaultColorer,
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ func newResourceView(title string, app *appView, list resource.List, c colorerFn
|
||||||
Pages: tview.NewPages(),
|
Pages: tview.NewPages(),
|
||||||
}
|
}
|
||||||
|
|
||||||
tv := newTableView(app, v.title, list.SortFn())
|
tv := newTableView(app, v.title)
|
||||||
{
|
{
|
||||||
tv.SetColorer(c)
|
tv.SetColorer(c)
|
||||||
tv.SetSelectionChangedFunc(v.selChanged)
|
tv.SetSelectionChangedFunc(v.selChanged)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rowSorter struct {
|
||||||
|
rows resource.Rows
|
||||||
|
index int
|
||||||
|
asc bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s rowSorter) Len() int {
|
||||||
|
return len(s.rows)
|
||||||
|
}
|
||||||
|
func (s rowSorter) Swap(i, j int) {
|
||||||
|
s.rows[i], s.rows[j] = s.rows[j], s.rows[i]
|
||||||
|
}
|
||||||
|
func (s rowSorter) Less(i, j int) bool {
|
||||||
|
c1 := s.rows[i][s.index]
|
||||||
|
c2 := s.rows[j][s.index]
|
||||||
|
|
||||||
|
if m1, ok := isMetric(c1); ok {
|
||||||
|
m2, _ := isMetric(c2)
|
||||||
|
i1, _ := strconv.Atoi(m1)
|
||||||
|
i2, _ := strconv.Atoi(m2)
|
||||||
|
if s.asc {
|
||||||
|
return i1 < i2
|
||||||
|
}
|
||||||
|
return i1 > i2
|
||||||
|
}
|
||||||
|
|
||||||
|
c := strings.Compare(c1, c2)
|
||||||
|
if s.asc {
|
||||||
|
return c < 0
|
||||||
|
}
|
||||||
|
return c > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
type groupSorter struct {
|
||||||
|
rows []string
|
||||||
|
asc bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s groupSorter) Len() int {
|
||||||
|
return len(s.rows)
|
||||||
|
}
|
||||||
|
func (s groupSorter) Swap(i, j int) {
|
||||||
|
s.rows[i], s.rows[j] = s.rows[j], s.rows[i]
|
||||||
|
}
|
||||||
|
func (s groupSorter) Less(i, j int) bool {
|
||||||
|
c1 := s.rows[i]
|
||||||
|
c2 := s.rows[j]
|
||||||
|
|
||||||
|
if m1, ok := isMetric(c1); ok {
|
||||||
|
m2, _ := isMetric(c2)
|
||||||
|
i1, _ := strconv.Atoi(m1)
|
||||||
|
i2, _ := strconv.Atoi(m2)
|
||||||
|
if s.asc {
|
||||||
|
return i1 < i2
|
||||||
|
}
|
||||||
|
return i1 > i2
|
||||||
|
}
|
||||||
|
|
||||||
|
c := strings.Compare(c1, c2)
|
||||||
|
if s.asc {
|
||||||
|
return c < 0
|
||||||
|
}
|
||||||
|
return c > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Helpers...
|
||||||
|
|
||||||
|
var metricRX = regexp.MustCompile(`\A(\d+)(m|Mi)\z`)
|
||||||
|
|
||||||
|
func isMetric(s string) (string, bool) {
|
||||||
|
if m := metricRX.FindStringSubmatch(s); len(m) == 3 {
|
||||||
|
return m[1], true
|
||||||
|
}
|
||||||
|
return s, false
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package views
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
|
@ -19,6 +20,15 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
sortFn func(rows resource.Rows, sortCol sortColumn)
|
||||||
|
cleanseFn func(string) string
|
||||||
|
|
||||||
|
sortColumn struct {
|
||||||
|
index int
|
||||||
|
colCount int
|
||||||
|
asc bool
|
||||||
|
}
|
||||||
|
|
||||||
tableView struct {
|
tableView struct {
|
||||||
*tview.Table
|
*tview.Table
|
||||||
|
|
||||||
|
|
@ -28,18 +38,20 @@ type (
|
||||||
refreshMX sync.Mutex
|
refreshMX sync.Mutex
|
||||||
actions keyActions
|
actions keyActions
|
||||||
colorerFn colorerFn
|
colorerFn colorerFn
|
||||||
sortFn resource.SortFn
|
sortFn sortFn
|
||||||
|
cleanseFn cleanseFn
|
||||||
data resource.TableData
|
data resource.TableData
|
||||||
cmdBuff *cmdBuff
|
cmdBuff *cmdBuff
|
||||||
|
sortBuff *cmdBuff
|
||||||
tableMX sync.Mutex
|
tableMX sync.Mutex
|
||||||
|
sortCol sortColumn
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func newTableView(app *appView, title string, sortFn resource.SortFn) *tableView {
|
func newTableView(app *appView, title string) *tableView {
|
||||||
v := tableView{app: app, Table: tview.NewTable()}
|
v := tableView{app: app, Table: tview.NewTable(), sortCol: sortColumn{0, 0, true}}
|
||||||
{
|
{
|
||||||
v.baseTitle = title
|
v.baseTitle = title
|
||||||
v.sortFn = sortFn
|
|
||||||
v.actions = make(keyActions)
|
v.actions = make(keyActions)
|
||||||
v.SetBorder(true)
|
v.SetBorder(true)
|
||||||
v.SetBorderColor(tcell.ColorDodgerBlue)
|
v.SetBorderColor(tcell.ColorDodgerBlue)
|
||||||
|
|
@ -53,9 +65,14 @@ func newTableView(app *appView, title string, sortFn resource.SortFn) *tableView
|
||||||
v.SetInputCapture(v.keyboard)
|
v.SetInputCapture(v.keyboard)
|
||||||
}
|
}
|
||||||
|
|
||||||
v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd, false)
|
v.actions[KeyShiftI] = newKeyAction("Invert", v.sortInvertCmd, true)
|
||||||
v.actions[tcell.KeyEnter] = newKeyAction("Search", v.filterCmd, false)
|
v.actions[KeyShiftN] = newKeyAction("Sort Name", v.sortColCmd(0), true)
|
||||||
v.actions[tcell.KeyEscape] = newKeyAction("Reset Filter", v.resetCmd, false)
|
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.KeyBackspace2] = newKeyAction("Erase", v.eraseCmd, false)
|
||||||
v.actions[KeyG] = newKeyAction("Top", app.puntCmd, false)
|
v.actions[KeyG] = newKeyAction("Top", app.puntCmd, false)
|
||||||
v.actions[KeyShiftG] = newKeyAction("Bottom", app.puntCmd, false)
|
v.actions[KeyShiftG] = newKeyAction("Bottom", app.puntCmd, false)
|
||||||
|
|
@ -128,13 +145,45 @@ func (v *tableView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *tableView) nameColIndex() int {
|
||||||
|
col := 0
|
||||||
|
if v.currentNS == resource.AllNamespaces {
|
||||||
|
col++
|
||||||
|
}
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *tableView) sortColCmd(col int) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
if col == -1 {
|
||||||
|
v.sortCol.index, v.sortCol.asc = v.GetColumnCount()-1, true
|
||||||
|
} else {
|
||||||
|
v.sortCol.index, v.sortCol.asc = v.nameColIndex()+col, true
|
||||||
|
}
|
||||||
|
|
||||||
|
v.refresh()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *tableView) sortNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
v.sortCol.index, v.sortCol.asc = 0, true
|
||||||
|
v.refresh()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *tableView) sortInvertCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
v.sortCol.asc = !v.sortCol.asc
|
||||||
|
v.refresh()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (v *tableView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
func (v *tableView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if v.app.cmdView.inCmdMode() {
|
if v.app.cmdView.inCmdMode() {
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
|
||||||
v.app.flash(flashInfo, "Filtering...")
|
v.app.flash(flashInfo, "Filtering...")
|
||||||
log.Info().Msg("Entering filtering mode...")
|
|
||||||
v.cmdBuff.reset()
|
v.cmdBuff.reset()
|
||||||
v.cmdBuff.setActive(true)
|
v.cmdBuff.setActive(true)
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -217,49 +266,151 @@ func (v *tableView) filtered() resource.TableData {
|
||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *tableView) displayCol(index int, name string) string {
|
||||||
|
if v.sortCol.index != index {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
order := "↓"
|
||||||
|
if v.sortCol.asc {
|
||||||
|
order = "↑"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s[green::]%s[::]", name, order)
|
||||||
|
}
|
||||||
|
|
||||||
func (v *tableView) doUpdate(data resource.TableData) {
|
func (v *tableView) doUpdate(data resource.TableData) {
|
||||||
v.Clear()
|
|
||||||
v.currentNS = data.Namespace
|
v.currentNS = data.Namespace
|
||||||
|
if v.currentNS == resource.AllNamespaces {
|
||||||
|
v.actions[KeyShiftG] = newKeyAction("Sort Namespace", v.sortNamespaceCmd, true)
|
||||||
|
} else {
|
||||||
|
delete(v.actions, KeyShiftS)
|
||||||
|
}
|
||||||
|
v.Clear()
|
||||||
|
|
||||||
|
// Going from namespace to non namespace or vice-versa?
|
||||||
|
switch {
|
||||||
|
case v.sortCol.colCount == 0:
|
||||||
|
case len(data.Header) > v.sortCol.colCount:
|
||||||
|
v.sortCol.index++
|
||||||
|
case len(data.Header) < v.sortCol.colCount:
|
||||||
|
v.sortCol.index--
|
||||||
|
}
|
||||||
|
v.sortCol.colCount = len(data.Header)
|
||||||
|
if v.sortCol.index < 0 {
|
||||||
|
v.sortCol.index = 0
|
||||||
|
}
|
||||||
|
|
||||||
var row int
|
var row int
|
||||||
for col, h := range data.Header {
|
for col, h := range data.Header {
|
||||||
c := tview.NewTableCell(h)
|
v.addHeaderCell(col, h)
|
||||||
{
|
|
||||||
c.SetExpansion(3)
|
|
||||||
if len(h) == 0 {
|
|
||||||
c.SetExpansion(1)
|
|
||||||
}
|
|
||||||
c.SetTextColor(tcell.ColorWhite)
|
|
||||||
}
|
|
||||||
v.SetCell(row, col, c)
|
|
||||||
}
|
}
|
||||||
row++
|
row++
|
||||||
|
|
||||||
keys := make([]string, 0, len(data.Rows))
|
keys := v.sortRows(data)
|
||||||
for k := range data.Rows {
|
groupKeys := map[string][]string{}
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
if v.sortFn != nil {
|
|
||||||
v.sortFn(keys)
|
|
||||||
}
|
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
|
grp := data.Rows[k].Fields[v.sortCol.index]
|
||||||
|
if s, ok := groupKeys[grp]; ok {
|
||||||
|
s = append(s, k)
|
||||||
|
groupKeys[grp] = s
|
||||||
|
} else {
|
||||||
|
groupKeys[grp] = []string{k}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performs secondary to sort by name for each groups.
|
||||||
|
gKeys := make([]string, len(keys))
|
||||||
|
for k, v := range groupKeys {
|
||||||
|
sort.Strings(v)
|
||||||
|
gKeys = append(gKeys, k)
|
||||||
|
}
|
||||||
|
rs := groupSorter{gKeys, v.sortCol.asc}
|
||||||
|
sort.Sort(rs)
|
||||||
|
|
||||||
|
for _, gk := range gKeys {
|
||||||
|
for _, k := range groupKeys[gk] {
|
||||||
fgColor := tcell.ColorGray
|
fgColor := tcell.ColorGray
|
||||||
if v.colorerFn != nil {
|
if v.colorerFn != nil {
|
||||||
fgColor = v.colorerFn(data.Namespace, data.Rows[k])
|
fgColor = v.colorerFn(data.Namespace, data.Rows[k])
|
||||||
}
|
}
|
||||||
for col, f := range data.Rows[k].Fields {
|
for col, field := range data.Rows[k].Fields {
|
||||||
c := tview.NewTableCell(deltas(data.Rows[k].Deltas[col], f))
|
v.addBodyCell(row, col, field, data.Rows[k].Deltas[col], fgColor)
|
||||||
{
|
|
||||||
c.SetExpansion(3)
|
|
||||||
if len(data.Header[col]) == 0 {
|
|
||||||
c.SetExpansion(1)
|
|
||||||
}
|
|
||||||
c.SetTextColor(fgColor)
|
|
||||||
}
|
|
||||||
v.SetCell(row, col, c)
|
|
||||||
}
|
}
|
||||||
row++
|
row++
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *tableView) addHeaderCell(col int, name string) {
|
||||||
|
c := tview.NewTableCell(v.displayCol(col, name))
|
||||||
|
{
|
||||||
|
c.SetExpansion(3)
|
||||||
|
if len(name) == 0 {
|
||||||
|
c.SetExpansion(1)
|
||||||
|
}
|
||||||
|
c.SetTextColor(tcell.ColorAntiqueWhite)
|
||||||
|
}
|
||||||
|
v.SetCell(0, col, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *tableView) addBodyCell(row, col int, field, delta string, color tcell.Color) {
|
||||||
|
c := tview.NewTableCell(deltas(delta, field))
|
||||||
|
{
|
||||||
|
c.SetExpansion(3)
|
||||||
|
if len(v.GetCell(0, col).Text) == 0 {
|
||||||
|
c.SetExpansion(1)
|
||||||
|
}
|
||||||
|
c.SetTextColor(color)
|
||||||
|
}
|
||||||
|
v.SetCell(row, col, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *tableView) defaultSort(rows resource.Rows) {
|
||||||
|
t := rowSorter{rows: rows, index: v.sortCol.index, asc: v.sortCol.asc}
|
||||||
|
sort.Sort(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *tableView) sortRows(data resource.TableData) []string {
|
||||||
|
rows := make(resource.Rows, 0, len(data.Rows))
|
||||||
|
for _, r := range data.Rows {
|
||||||
|
rows = append(rows, r.Fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.sortFn != nil {
|
||||||
|
v.sortFn(rows, v.sortCol)
|
||||||
|
} else {
|
||||||
|
v.defaultSort(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make([]string, len(rows))
|
||||||
|
for i, r := range rows {
|
||||||
|
col, prefix := 0, v.currentNS
|
||||||
|
switch v.currentNS {
|
||||||
|
case resource.AllNamespaces:
|
||||||
|
col, prefix = 1, r[0]
|
||||||
|
case resource.NotNamespaced:
|
||||||
|
prefix = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
key := r[col]
|
||||||
|
if v.cleanseFn != nil {
|
||||||
|
key = v.cleanseFn(key)
|
||||||
|
} else {
|
||||||
|
key = v.defaultColCleanse(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(prefix) == 0 {
|
||||||
|
keys[i] = key
|
||||||
|
} else {
|
||||||
|
keys[i] = prefix + "/" + key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*tableView) defaultColCleanse(s string) string {
|
||||||
|
return strings.TrimSpace(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *tableView) resetTitle() {
|
func (v *tableView) resetTitle() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue