parent
77ffacc2e4
commit
5c1ff0ed7b
2
Makefile
2
Makefile
|
|
@ -3,7 +3,7 @@ PACKAGE := github.com/derailed/$(NAME)
|
|||
GIT := $(shell git rev-parse --short HEAD)
|
||||
SOURCE_DATE_EPOCH ?= $(shell date +%s)
|
||||
DATE := $(shell date -u -d @${SOURCE_DATE_EPOCH} +%FT%T%Z)
|
||||
VERSION ?= v0.23.1
|
||||
VERSION ?= v0.23.2
|
||||
IMG_NAME := derailed/k9s
|
||||
IMAGE := ${IMG_NAME}:${VERSION}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.23.2
|
||||
|
||||
## Notes
|
||||
|
||||
Thank you to all that contributed with flushing out issues and enhancements for 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. Your support, kindness and awesome suggestions to make K9s better are as ever very much noted and appreciated!
|
||||
|
||||
If you feel K9s is helping your Kubernetes journey, please consider joining our [sponsorhip program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||
|
||||
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
|
||||
|
||||
---
|
||||
|
||||
### Write Mode
|
||||
|
||||
K9s is writable by default, meaning you can interact with your cluster and make changes using one shot commands ie edit, delete, scale, etc... There `readOnly` config option that can be specified in the configuration or via a cli arg to override this behavior. In this drop, we're introducing a symmetrical command line arg aka `--write` that overrides a K9s session and make it writable tho the readOnly config option is set to true.
|
||||
|
||||
## Inverse Log Filtering
|
||||
|
||||
In the last drop, we've introduces reverse filters to filter out resources from table views. Now you will be able to apply inverse filtering on your log views as well via `/!fred`
|
||||
|
||||
---
|
||||
|
||||
## Resolved Issues/Features
|
||||
|
||||
* [Issue #906](https://github.com/derailed/k9s/issues/906) Print resources in pod view. With Feelings. Thanks Claudio!
|
||||
* [Issue #889](https://github.com/derailed/k9s/issues/889) Disable readOnly config
|
||||
* [Issue #564](https://github.com/derailed/k9s/issues/564) Invert filter mode on logs
|
||||
|
||||
## Resolved PRs
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
48
cmd/root.go
48
cmd/root.go
|
|
@ -28,7 +28,6 @@ var (
|
|||
version, commit, date = "dev", "dev", client.NA
|
||||
k9sFlags *config.Flags
|
||||
k8sFlags *genericclioptions.ConfigFlags
|
||||
demoMode = new(bool)
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: appName,
|
||||
|
|
@ -40,7 +39,6 @@ var (
|
|||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd(), infoCmd())
|
||||
initTransientFlags()
|
||||
initK9sFlags()
|
||||
initK8sFlags()
|
||||
|
||||
|
|
@ -105,28 +103,15 @@ func loadConfiguration() *config.Config {
|
|||
log.Warn().Msg("Unable to locate K9s config. Generating new configuration...")
|
||||
}
|
||||
|
||||
if demoMode != nil {
|
||||
k9sCfg.SetDemoMode(*demoMode)
|
||||
}
|
||||
if *k9sFlags.RefreshRate != config.DefaultRefreshRate {
|
||||
k9sCfg.K9s.OverrideRefreshRate(*k9sFlags.RefreshRate)
|
||||
}
|
||||
|
||||
if k9sFlags.Headless != nil {
|
||||
k9sCfg.K9s.OverrideHeadless(*k9sFlags.Headless)
|
||||
}
|
||||
|
||||
if k9sFlags.Crumbsless != nil {
|
||||
k9sCfg.K9s.OverrideCrumbsless(*k9sFlags.Crumbsless)
|
||||
}
|
||||
|
||||
if k9sFlags.ReadOnly != nil {
|
||||
k9sCfg.K9s.OverrideReadOnly(*k9sFlags.ReadOnly)
|
||||
}
|
||||
|
||||
if k9sFlags.Command != nil {
|
||||
k9sCfg.K9s.OverrideCommand(*k9sFlags.Command)
|
||||
}
|
||||
k9sCfg.K9s.OverrideHeadless(*k9sFlags.Headless)
|
||||
k9sCfg.K9s.OverrideCrumbsless(*k9sFlags.Crumbsless)
|
||||
k9sCfg.K9s.OverrideReadOnly(*k9sFlags.ReadOnly)
|
||||
k9sCfg.K9s.OverrideWrite(*k9sFlags.Write)
|
||||
k9sCfg.K9s.OverrideCommand(*k9sFlags.Command)
|
||||
|
||||
if isBoolSet(k9sFlags.AllNamespaces) && k9sCfg.SetActiveNamespace(client.AllNamespaces) != nil {
|
||||
log.Error().Msg("Setting active namespace")
|
||||
|
|
@ -175,15 +160,6 @@ func parseLevel(level string) zerolog.Level {
|
|||
}
|
||||
}
|
||||
|
||||
func initTransientFlags() {
|
||||
rootCmd.Flags().BoolVar(
|
||||
demoMode,
|
||||
"demo",
|
||||
false,
|
||||
"Enable demo mode to show keyboard commands",
|
||||
)
|
||||
}
|
||||
|
||||
func initK9sFlags() {
|
||||
k9sFlags = config.NewFlags()
|
||||
rootCmd.Flags().IntVarP(
|
||||
|
|
@ -204,6 +180,12 @@ func initK9sFlags() {
|
|||
false,
|
||||
"Turn K9s header off",
|
||||
)
|
||||
rootCmd.Flags().BoolVar(
|
||||
k9sFlags.Crumbsless,
|
||||
"crumbsless",
|
||||
false,
|
||||
"Turn K9s crumbs off",
|
||||
)
|
||||
rootCmd.Flags().BoolVarP(
|
||||
k9sFlags.AllNamespaces,
|
||||
"all-namespaces", "A",
|
||||
|
|
@ -220,13 +202,13 @@ func initK9sFlags() {
|
|||
k9sFlags.ReadOnly,
|
||||
"readonly",
|
||||
false,
|
||||
"Toggles readOnly mode by overriding configuration setting",
|
||||
"Sets readOnly mode by overriding readOnly configuration setting",
|
||||
)
|
||||
rootCmd.Flags().BoolVar(
|
||||
k9sFlags.Crumbsless,
|
||||
"crumbsless",
|
||||
k9sFlags.Write,
|
||||
"write",
|
||||
false,
|
||||
"Turn K9s crumbs off",
|
||||
"Sets write mode by overriding the readOnly configuration setting",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ type (
|
|||
K9s *K9s `yaml:"k9s"`
|
||||
client client.Connection
|
||||
settings KubeSettings
|
||||
demoMode bool
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -70,16 +69,6 @@ func NewConfig(ks KubeSettings) *Config {
|
|||
return &Config{K9s: NewK9s(), settings: ks}
|
||||
}
|
||||
|
||||
// DemoMode returns true if demo mode is active, false otherwise.
|
||||
func (c *Config) DemoMode() bool {
|
||||
return c.demoMode
|
||||
}
|
||||
|
||||
// SetDemoMode sets the demo mode.
|
||||
func (c *Config) SetDemoMode(b bool) {
|
||||
c.demoMode = b
|
||||
}
|
||||
|
||||
// Refine the configuration based on cli args.
|
||||
func (c *Config) Refine(flags *genericclioptions.ConfigFlags) error {
|
||||
cfg, err := flags.ToRawKubeConfigLoader().RawConfig()
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ type Flags struct {
|
|||
Command *string
|
||||
AllNamespaces *bool
|
||||
ReadOnly *bool
|
||||
Write *bool
|
||||
Crumbsless *bool
|
||||
}
|
||||
|
||||
|
|
@ -31,6 +32,7 @@ func NewFlags() *Flags {
|
|||
Command: strPtr(DefaultCommand),
|
||||
AllNamespaces: boolPtr(false),
|
||||
ReadOnly: boolPtr(false),
|
||||
Write: boolPtr(false),
|
||||
Crumbsless: boolPtr(false),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package config
|
||||
|
||||
import "github.com/derailed/k9s/internal/client"
|
||||
import (
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRefreshRate = 2
|
||||
|
|
@ -56,7 +58,17 @@ func (k *K9s) OverrideCrumbsless(b bool) {
|
|||
|
||||
// OverrideReadOnly set the readonly mode manually.
|
||||
func (k *K9s) OverrideReadOnly(b bool) {
|
||||
k.manualReadOnly = &b
|
||||
if b {
|
||||
k.manualReadOnly = &b
|
||||
}
|
||||
}
|
||||
|
||||
// OverrideWrite set the write mode manually.
|
||||
func (k *K9s) OverrideWrite(b bool) {
|
||||
if b {
|
||||
flag := !b
|
||||
k.manualReadOnly = &flag
|
||||
}
|
||||
}
|
||||
|
||||
// OverrideCommand set the command manually.
|
||||
|
|
@ -100,6 +112,7 @@ func (k *K9s) IsReadOnly() bool {
|
|||
if k.manualReadOnly != nil {
|
||||
readOnly = *k.manualReadOnly
|
||||
}
|
||||
|
||||
return readOnly
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,57 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsReadOnly(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
config string
|
||||
read, write bool
|
||||
readOnly bool
|
||||
}{
|
||||
"writable": {
|
||||
config: "k9s.yml",
|
||||
},
|
||||
"writable_read_override": {
|
||||
config: "k9s.yml",
|
||||
read: true,
|
||||
readOnly: true,
|
||||
},
|
||||
"writable_write_override": {
|
||||
config: "k9s.yml",
|
||||
write: true,
|
||||
},
|
||||
"readonly": {
|
||||
config: "k9s_readonly.yml",
|
||||
readOnly: true,
|
||||
},
|
||||
"readonly_read_override": {
|
||||
config: "k9s_readonly.yml",
|
||||
read: true,
|
||||
readOnly: true,
|
||||
},
|
||||
"readonly_write_override": {
|
||||
config: "k9s_readonly.yml",
|
||||
write: true,
|
||||
},
|
||||
"readonly_both_override": {
|
||||
config: "k9s_readonly.yml",
|
||||
read: true,
|
||||
write: true,
|
||||
},
|
||||
}
|
||||
|
||||
mk := NewMockKubeSettings()
|
||||
cfg := config.NewConfig(mk)
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Nil(t, cfg.Load("testdata/"+u.config))
|
||||
cfg.K9s.OverrideReadOnly(u.read)
|
||||
cfg.K9s.OverrideWrite(u.write)
|
||||
assert.Equal(t, u.readOnly, cfg.K9s.IsReadOnly())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestK9sValidate(t *testing.T) {
|
||||
mc := NewMockConnection()
|
||||
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
k9s:
|
||||
refreshRate: 2
|
||||
readOnly: false
|
||||
logger:
|
||||
tail: 200
|
||||
buffer: 2000
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
k9s:
|
||||
refreshRate: 2
|
||||
readOnly: true
|
||||
logger:
|
||||
tail: 200
|
||||
buffer: 2000
|
||||
currentContext: minikube
|
||||
currentCluster: minikube
|
||||
clusters:
|
||||
minikube:
|
||||
namespace:
|
||||
active: kube-system
|
||||
favorites:
|
||||
- default
|
||||
- kube-public
|
||||
- istio-system
|
||||
- all
|
||||
- kube-system
|
||||
view:
|
||||
active: ctx
|
||||
fred:
|
||||
namespace:
|
||||
active: default
|
||||
favorites:
|
||||
- default
|
||||
- kube-public
|
||||
- istio-system
|
||||
- all
|
||||
- kube-system
|
||||
view:
|
||||
active: po
|
||||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"errors"
|
||||
"math"
|
||||
"regexp"
|
||||
|
||||
"github.com/derailed/tview"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
|
|
@ -13,6 +14,19 @@ import (
|
|||
"k8s.io/cli-runtime/pkg/printers"
|
||||
)
|
||||
|
||||
var (
|
||||
inverseRx = regexp.MustCompile(`\A\!`)
|
||||
fuzzyRx = regexp.MustCompile(`\A\-f`)
|
||||
)
|
||||
|
||||
// IsInverseSelector checks if inverse char has been provided.
|
||||
func IsInverseSelector(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
return inverseRx.MatchString(s)
|
||||
}
|
||||
|
||||
// IsFuzzySelector checks if filter is fuzzy or not.
|
||||
func IsFuzzySelector(s string) bool {
|
||||
if s == "" {
|
||||
|
|
|
|||
|
|
@ -180,8 +180,6 @@ func (l LogItems) Filter(q string, showTime bool) ([]int, [][]int, error) {
|
|||
return matches, indices, nil
|
||||
}
|
||||
|
||||
var fuzzyRx = regexp.MustCompile(`\A\-f`)
|
||||
|
||||
func (l LogItems) fuzzyFilter(q string, showTime bool) ([]int, [][]int) {
|
||||
q = strings.TrimSpace(q)
|
||||
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
|
||||
|
|
@ -195,22 +193,32 @@ func (l LogItems) fuzzyFilter(q string, showTime bool) ([]int, [][]int) {
|
|||
}
|
||||
|
||||
func (l LogItems) filterLogs(q string, showTime bool) ([]int, [][]int, error) {
|
||||
var invert bool
|
||||
if IsInverseSelector(q) {
|
||||
invert = true
|
||||
q = q[1:]
|
||||
}
|
||||
rx, err := regexp.Compile(`(?i)` + q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
|
||||
for i, line := range l.Lines(showTime) {
|
||||
if locs := rx.FindIndex(line); locs != nil {
|
||||
matches = append(matches, i)
|
||||
ii := make([]int, 0, 10)
|
||||
for i := 0; i < len(locs); i += 2 {
|
||||
for j := locs[i]; j < locs[i+1]; j++ {
|
||||
ii = append(ii, j)
|
||||
}
|
||||
}
|
||||
indices = append(indices, ii)
|
||||
locs := rx.FindIndex(line)
|
||||
if locs != nil && invert {
|
||||
continue
|
||||
}
|
||||
if locs == nil && !invert {
|
||||
continue
|
||||
}
|
||||
matches = append(matches, i)
|
||||
ii := make([]int, 0, 10)
|
||||
for i := 0; i < len(locs); i += 2 {
|
||||
for j := locs[i]; j < locs[i+1]; j++ {
|
||||
ii = append(ii, j)
|
||||
}
|
||||
}
|
||||
indices = append(indices, ii)
|
||||
}
|
||||
|
||||
return matches, indices, nil
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ type Log struct {
|
|||
filter string
|
||||
lastSent int
|
||||
flushTimeout time.Duration
|
||||
filtering bool
|
||||
}
|
||||
|
||||
// NewLog returns a new model.
|
||||
|
|
@ -176,28 +175,15 @@ func (l *Log) Filter(q string) {
|
|||
defer l.mx.Unlock()
|
||||
|
||||
if len(q) == 0 {
|
||||
l.filter, l.filtering = "", false
|
||||
l.filter = ""
|
||||
l.fireLogCleared()
|
||||
l.fireLogBuffChanged(l.lines)
|
||||
return
|
||||
}
|
||||
|
||||
l.filter = q
|
||||
// BOZO!! No needed since cmdbuff is now throttled!!
|
||||
if l.filtering {
|
||||
return
|
||||
}
|
||||
l.filtering = true
|
||||
go func(l *Log) {
|
||||
<-time.After(500 * time.Millisecond)
|
||||
l.fireLogCleared()
|
||||
l.fireLogBuffChanged(l.lines)
|
||||
l.mx.Lock()
|
||||
{
|
||||
l.filtering = false
|
||||
}
|
||||
l.mx.Unlock()
|
||||
}(l)
|
||||
l.fireLogCleared()
|
||||
l.fireLogBuffChanged(l.lines)
|
||||
}
|
||||
|
||||
func (l *Log) load() error {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ func TestLogFilter(t *testing.T) {
|
|||
q: `pod-line-[1-3]{1}`,
|
||||
e: 4,
|
||||
},
|
||||
"invert": {
|
||||
q: `!pod-line-1`,
|
||||
e: 8,
|
||||
},
|
||||
"fuzzy": {
|
||||
q: `-f po-l1`,
|
||||
e: 2,
|
||||
|
|
@ -75,13 +79,13 @@ func TestLogFilter(t *testing.T) {
|
|||
|
||||
m.Notify()
|
||||
assert.Equal(t, 1, v.dataCalled)
|
||||
assert.Equal(t, 1, v.clearCalled)
|
||||
assert.Equal(t, 2, v.clearCalled)
|
||||
assert.Equal(t, 0, v.errCalled)
|
||||
assert.Equal(t, u.e, len(v.data))
|
||||
|
||||
m.ClearFilter()
|
||||
assert.Equal(t, 2, v.dataCalled)
|
||||
assert.Equal(t, 2, v.clearCalled)
|
||||
assert.Equal(t, 3, v.clearCalled)
|
||||
assert.Equal(t, 0, v.errCalled)
|
||||
assert.Equal(t, size, len(v.data))
|
||||
})
|
||||
|
|
@ -195,7 +199,7 @@ func TestLogTimedout(t *testing.T) {
|
|||
}
|
||||
m.Notify()
|
||||
assert.Equal(t, 1, v.dataCalled)
|
||||
assert.Equal(t, 1, v.clearCalled)
|
||||
assert.Equal(t, 2, v.clearCalled)
|
||||
assert.Equal(t, 0, v.errCalled)
|
||||
const e = "\x1b[38;5;209ml\x1b[0m\x1b[38;5;209mi\x1b[0m\x1b[38;5;209mn\x1b[0m\x1b[38;5;209me\x1b[0m\x1b[38;5;209m1\x1b[0m"
|
||||
assert.Equal(t, e, string(v.data[0]))
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package model
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
|
|
@ -27,10 +26,6 @@ func NewPulseHealth(f dao.Factory) *PulseHealth {
|
|||
|
||||
// List returns a canned collection of resources health.
|
||||
func (h *PulseHealth) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||
defer func(t time.Time) {
|
||||
log.Debug().Msgf("PulseHealthCheck %v", time.Since(t))
|
||||
}(time.Now())
|
||||
|
||||
gvrs := []string{
|
||||
"v1/pods",
|
||||
"v1/events",
|
||||
|
|
|
|||
|
|
@ -145,6 +145,11 @@ func (h Header) HasAge() bool {
|
|||
return h.IndexOf(ageCol, true) != -1
|
||||
}
|
||||
|
||||
// IsMetricsCol checks if given column index represents metrics.
|
||||
func (h Header) IsMetricsCol(col int) bool {
|
||||
return h[col].MX
|
||||
}
|
||||
|
||||
// IsAgeCol checks if given column index is the age column.
|
||||
func (h Header) IsAgeCol(col int) bool {
|
||||
if !h.HasAge() || col >= len(h) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package render
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
|
@ -259,30 +260,26 @@ func mapToIfc(m interface{}) (s string) {
|
|||
|
||||
func toMcPerc(v1, v2 *resource.Quantity) string {
|
||||
m := v1.MilliValue()
|
||||
return toMc(m) + " (" +
|
||||
strconv.Itoa(client.ToPercentage(m, v2.MilliValue())) + "%)"
|
||||
return fmt.Sprintf("%s (%d%%)", toMc(m), client.ToPercentage(m, v2.MilliValue()))
|
||||
}
|
||||
|
||||
func toMiPerc(v1, v2 *resource.Quantity) string {
|
||||
m := v1.Value()
|
||||
return toMi(m) + " (" +
|
||||
strconv.Itoa(client.ToPercentage(m, v2.Value())) + "%)"
|
||||
return fmt.Sprintf("%s (%d%%)", toMi(m), client.ToPercentage(m, v2.Value()))
|
||||
}
|
||||
|
||||
func toMc(v int64) string {
|
||||
if v == 0 {
|
||||
return ZeroValue
|
||||
}
|
||||
p := message.NewPrinter(language.English)
|
||||
return p.Sprintf("%d", v)
|
||||
return AsThousands(v)
|
||||
}
|
||||
|
||||
func toMi(v int64) string {
|
||||
if v == 0 {
|
||||
return ZeroValue
|
||||
}
|
||||
p := message.NewPrinter(language.English)
|
||||
return p.Sprintf("%d", client.ToMB(v))
|
||||
return AsThousands(client.ToMB(v))
|
||||
}
|
||||
|
||||
func boolPtrToStr(b *bool) string {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fvbommel/sortorder"
|
||||
|
|
@ -146,8 +147,14 @@ func (rr Rows) Find(id string) (int, bool) {
|
|||
}
|
||||
|
||||
// Sort rows based on column index and order.
|
||||
func (rr Rows) Sort(col int, asc bool) {
|
||||
t := RowSorter{Rows: rr, Index: col, Asc: asc}
|
||||
func (rr Rows) Sort(col int, asc, isNum, isDur bool) {
|
||||
t := RowSorter{
|
||||
Rows: rr,
|
||||
Index: col,
|
||||
IsNumber: isNum,
|
||||
IsDuration: isDur,
|
||||
Asc: asc,
|
||||
}
|
||||
sort.Sort(t)
|
||||
}
|
||||
|
||||
|
|
@ -155,9 +162,10 @@ func (rr Rows) Sort(col int, asc bool) {
|
|||
|
||||
// RowSorter sorts rows.
|
||||
type RowSorter struct {
|
||||
Rows Rows
|
||||
Index int
|
||||
Asc bool
|
||||
Rows Rows
|
||||
Index int
|
||||
IsNumber, IsDuration bool
|
||||
Asc bool
|
||||
}
|
||||
|
||||
func (s RowSorter) Len() int {
|
||||
|
|
@ -169,7 +177,7 @@ func (s RowSorter) Swap(i, j int) {
|
|||
}
|
||||
|
||||
func (s RowSorter) Less(i, j int) bool {
|
||||
return Less(s.Asc, s.Rows[i].Fields[s.Index], s.Rows[j].Fields[s.Index])
|
||||
return Less(s.Asc, s.IsNumber, s.IsDuration, s.Rows[i].Fields[s.Index], s.Rows[j].Fields[s.Index])
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -185,8 +193,13 @@ func toAgeDuration(dur string) string {
|
|||
}
|
||||
|
||||
// Less return true if c1 < c2.
|
||||
func Less(asc bool, c1, c2 string) bool {
|
||||
c1, c2 = toAgeDuration(c1), toAgeDuration(c2)
|
||||
func Less(asc, isNumber, isDuration bool, c1, c2 string) bool {
|
||||
if isNumber {
|
||||
c1, c2 = strings.Replace(c1, ",", "", -1), strings.Replace(c2, ",", "", -1)
|
||||
}
|
||||
if isDuration {
|
||||
c1, c2 = toAgeDuration(c1), toAgeDuration(c2)
|
||||
}
|
||||
b := sortorder.NaturalLess(c1, c2)
|
||||
if asc {
|
||||
return b
|
||||
|
|
|
|||
|
|
@ -196,12 +196,19 @@ func (r RowEvents) FindIndex(id string) (int, bool) {
|
|||
}
|
||||
|
||||
// Sort rows based on column index and order.
|
||||
func (r RowEvents) Sort(ns string, sortCol int, ageCol bool, asc bool) {
|
||||
func (r RowEvents) Sort(ns string, sortCol int, ageCol, numCol, asc bool) {
|
||||
if sortCol == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
t := RowEventSorter{NS: ns, Events: r, Index: sortCol, Asc: asc}
|
||||
t := RowEventSorter{
|
||||
NS: ns,
|
||||
Events: r,
|
||||
Index: sortCol,
|
||||
Asc: asc,
|
||||
IsNumber: numCol,
|
||||
IsDuration: ageCol,
|
||||
}
|
||||
sort.Sort(t)
|
||||
|
||||
iids, fields := map[string][]string{}, make(StringSet, 0, len(r))
|
||||
|
|
@ -227,10 +234,12 @@ func (r RowEvents) Sort(ns string, sortCol int, ageCol bool, asc bool) {
|
|||
|
||||
// RowEventSorter sorts row events by a given colon.
|
||||
type RowEventSorter struct {
|
||||
Events RowEvents
|
||||
Index int
|
||||
NS string
|
||||
Asc bool
|
||||
Events RowEvents
|
||||
Index int
|
||||
NS string
|
||||
IsNumber bool
|
||||
IsDuration bool
|
||||
Asc bool
|
||||
}
|
||||
|
||||
func (r RowEventSorter) Len() int {
|
||||
|
|
@ -243,7 +252,7 @@ func (r RowEventSorter) Swap(i, j int) {
|
|||
|
||||
func (r RowEventSorter) Less(i, j int) bool {
|
||||
f1, f2 := r.Events[i].Row.Fields, r.Events[j].Row.Fields
|
||||
return Less(r.Asc, f1[r.Index], f2[r.Index])
|
||||
return Less(r.Asc, r.IsNumber, r.IsDuration, f1[r.Index], f2[r.Index])
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -409,10 +409,10 @@ func TestRowEventsDelete(t *testing.T) {
|
|||
|
||||
func TestRowEventsSort(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
re render.RowEvents
|
||||
col int
|
||||
age, asc bool
|
||||
e render.RowEvents
|
||||
re render.RowEvents
|
||||
col int
|
||||
age, num, asc bool
|
||||
e render.RowEvents
|
||||
}{
|
||||
"age_time": {
|
||||
re: render.RowEvents{
|
||||
|
|
@ -483,7 +483,7 @@ func TestRowEventsSort(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
u.re.Sort("", u.col, u.age, u.asc)
|
||||
u.re.Sort("", u.col, u.age, u.num, u.asc)
|
||||
assert.Equal(t, u.e, u.re)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -231,10 +231,10 @@ func TestRowsUpsert(t *testing.T) {
|
|||
|
||||
func TestRowsSortText(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
rows render.Rows
|
||||
col int
|
||||
asc bool
|
||||
e render.Rows
|
||||
rows render.Rows
|
||||
col int
|
||||
asc, num bool
|
||||
e render.Rows
|
||||
}{
|
||||
"plainAsc": {
|
||||
rows: render.Rows{
|
||||
|
|
@ -266,6 +266,7 @@ func TestRowsSortText(t *testing.T) {
|
|||
{Fields: []string{"1", "blee"}},
|
||||
},
|
||||
col: 0,
|
||||
num: true,
|
||||
asc: true,
|
||||
e: render.Rows{
|
||||
{Fields: []string{"1", "blee"}},
|
||||
|
|
@ -278,6 +279,7 @@ func TestRowsSortText(t *testing.T) {
|
|||
{Fields: []string{"1", "blee"}},
|
||||
},
|
||||
col: 0,
|
||||
num: true,
|
||||
asc: false,
|
||||
e: render.Rows{
|
||||
{Fields: []string{"10", "duh"}},
|
||||
|
|
@ -301,7 +303,7 @@ func TestRowsSortText(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
u.rows.Sort(u.col, u.asc)
|
||||
u.rows.Sort(u.col, u.asc, u.num, false)
|
||||
assert.Equal(t, u.e, u.rows)
|
||||
})
|
||||
}
|
||||
|
|
@ -342,7 +344,7 @@ func TestRowsSortDuration(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
u.rows.Sort(u.col, u.asc)
|
||||
u.rows.Sort(u.col, u.asc, false, true)
|
||||
assert.Equal(t, u.e, u.rows)
|
||||
})
|
||||
}
|
||||
|
|
@ -369,13 +371,13 @@ func TestRowsSortMetrics(t *testing.T) {
|
|||
},
|
||||
"metricDesc": {
|
||||
rows: render.Rows{
|
||||
{Fields: []string{"10m", "100Mi"}},
|
||||
{Fields: []string{"10000m", "1000Mi"}},
|
||||
{Fields: []string{"1m", "50Mi"}},
|
||||
},
|
||||
col: 1,
|
||||
asc: false,
|
||||
e: render.Rows{
|
||||
{Fields: []string{"10m", "100Mi"}},
|
||||
{Fields: []string{"10000m", "1000Mi"}},
|
||||
{Fields: []string{"1m", "50Mi"}},
|
||||
},
|
||||
},
|
||||
|
|
@ -384,7 +386,7 @@ func TestRowsSortMetrics(t *testing.T) {
|
|||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
u.rows.Sort(u.col, u.asc)
|
||||
u.rows.Sort(u.col, u.asc, true, false)
|
||||
assert.Equal(t, u.e, u.rows)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -234,10 +234,12 @@ func (t *Table) doUpdate(data render.TableData) {
|
|||
c.SetTextColor(fg)
|
||||
col++
|
||||
}
|
||||
colIndex := custData.Header.IndexOf(t.sortCol.name, false)
|
||||
custData.RowEvents.Sort(
|
||||
custData.Namespace,
|
||||
custData.Header.IndexOf(t.sortCol.name, false),
|
||||
colIndex,
|
||||
t.sortCol.name == "AGE",
|
||||
data.Header.IsMetricsCol(colIndex),
|
||||
t.sortCol.asc,
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue