refactor + cleanup
parent
81ef0ae0ec
commit
5005d8dc85
|
|
@ -235,6 +235,52 @@ func (v *benchView) hydrate() resource.TableData {
|
|||
return data
|
||||
}
|
||||
|
||||
func (v *benchView) getTV() *tableView {
|
||||
if vu, ok := v.GetPrimitive("table").(*tableView); ok {
|
||||
return vu
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *benchView) getDetails() *detailsView {
|
||||
if vu, ok := v.GetPrimitive("details").(*detailsView); ok {
|
||||
return vu
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *benchView) resetTitle() {
|
||||
v.SetTitle(fmt.Sprintf(benchTitleFmt, benchTitle, v.getTV().GetRowCount()-1))
|
||||
}
|
||||
|
||||
func (v *benchView) watchBenchDir(ctx context.Context) error {
|
||||
w, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case evt := <-w.Events:
|
||||
log.Debug().Msgf("Bench event %#v", evt)
|
||||
v.app.QueueUpdateDraw(func() {
|
||||
v.refresh()
|
||||
})
|
||||
case err := <-w.Errors:
|
||||
log.Info().Err(err).Msg("Dir Watcher failed")
|
||||
return
|
||||
case <-ctx.Done():
|
||||
log.Debug().Msg("!!!! FS WATCHER DONE!!")
|
||||
w.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return w.Add(v.benchDir())
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
|
|
@ -302,49 +348,3 @@ func augmentRow(fields resource.Row, data string) {
|
|||
fields[col] = asNum(sum)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *benchView) resetTitle() {
|
||||
v.SetTitle(fmt.Sprintf(benchTitleFmt, benchTitle, v.getTV().GetRowCount()-1))
|
||||
}
|
||||
|
||||
func (v *benchView) watchBenchDir(ctx context.Context) error {
|
||||
w, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case evt := <-w.Events:
|
||||
log.Debug().Msgf("Bench event %#v", evt)
|
||||
v.app.QueueUpdateDraw(func() {
|
||||
v.refresh()
|
||||
})
|
||||
case err := <-w.Errors:
|
||||
log.Info().Err(err).Msg("Dir Watcher failed")
|
||||
return
|
||||
case <-ctx.Done():
|
||||
log.Debug().Msg("!!!! FS WATCHER DONE!!")
|
||||
w.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return w.Add(filepath.Join(K9sBenchDir, v.app.config.K9s.CurrentCluster))
|
||||
}
|
||||
|
||||
func (v *benchView) getTV() *tableView {
|
||||
if vu, ok := v.GetPrimitive("table").(*tableView); ok {
|
||||
return vu
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *benchView) getDetails() *detailsView {
|
||||
if vu, ok := v.GetPrimitive("details").(*detailsView); ok {
|
||||
return vu
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,53 +18,56 @@ import (
|
|||
|
||||
const noSelection = ""
|
||||
|
||||
type updatable interface {
|
||||
restartUpdates()
|
||||
stopUpdates()
|
||||
update(context.Context)
|
||||
}
|
||||
type (
|
||||
updatable interface {
|
||||
restartUpdates()
|
||||
stopUpdates()
|
||||
update(context.Context)
|
||||
}
|
||||
|
||||
type resourceView struct {
|
||||
*tview.Pages
|
||||
masterDetail struct {
|
||||
*tview.Pages
|
||||
|
||||
app *appView
|
||||
title string
|
||||
selectedItem string
|
||||
selectedRow int
|
||||
namespaces map[int]string
|
||||
selectedNS string
|
||||
list resource.List
|
||||
enterFn enterFn
|
||||
extraActionsFn func(keyActions)
|
||||
selectedFn func() string
|
||||
decorateFn decorateFn
|
||||
colorerFn colorerFn
|
||||
actions keyActions
|
||||
nsListAccess bool
|
||||
path *string
|
||||
cancelFn context.CancelFunc
|
||||
parentCtx context.Context
|
||||
}
|
||||
app *appView
|
||||
actions keyActions
|
||||
title string
|
||||
selectedItem string
|
||||
selectedRow int
|
||||
selectedNS string
|
||||
path *string
|
||||
}
|
||||
|
||||
func (v *resourceView) filterResource(sel string) {
|
||||
v.list.SetLabelSelector(sel)
|
||||
v.refresh()
|
||||
}
|
||||
resourceView struct {
|
||||
*masterDetail
|
||||
|
||||
namespaces map[int]string
|
||||
list resource.List
|
||||
enterFn enterFn
|
||||
extraActionsFn func(keyActions)
|
||||
selectedFn func() string
|
||||
decorateFn decorateFn
|
||||
colorerFn colorerFn
|
||||
nsListAccess bool
|
||||
cancelFn context.CancelFunc
|
||||
parentCtx context.Context
|
||||
}
|
||||
)
|
||||
|
||||
func newResourceView(title string, app *appView, list resource.List) resourceViewer {
|
||||
v := resourceView{
|
||||
app: app,
|
||||
title: title,
|
||||
actions: make(keyActions),
|
||||
list: list,
|
||||
selectedNS: list.GetNamespace(),
|
||||
Pages: tview.NewPages(),
|
||||
masterDetail: &masterDetail{
|
||||
Pages: tview.NewPages(),
|
||||
app: app,
|
||||
title: title,
|
||||
actions: make(keyActions),
|
||||
selectedNS: list.GetNamespace(),
|
||||
},
|
||||
list: list,
|
||||
}
|
||||
|
||||
tv := newTableView(app, v.title)
|
||||
tv.SetSelectionChangedFunc(v.selChanged)
|
||||
tv.filterChanged(v.filterResource)
|
||||
|
||||
v.AddPage(v.list.GetName(), tv, true, true)
|
||||
|
||||
details := newDetailsView(app, v.backCmd)
|
||||
|
|
@ -73,22 +76,6 @@ func newResourceView(title string, app *appView, list resource.List) resourceVie
|
|||
return &v
|
||||
}
|
||||
|
||||
func (v *resourceView) stopUpdates() {
|
||||
if v.cancelFn != nil {
|
||||
v.cancelFn()
|
||||
}
|
||||
}
|
||||
|
||||
func (v *resourceView) restartUpdates() {
|
||||
if v.cancelFn != nil {
|
||||
v.cancelFn()
|
||||
}
|
||||
|
||||
var vctx context.Context
|
||||
vctx, v.cancelFn = context.WithCancel(v.parentCtx)
|
||||
v.update(vctx)
|
||||
}
|
||||
|
||||
// Init watches all running pods in given namespace
|
||||
func (v *resourceView) init(ctx context.Context, ns string) {
|
||||
v.parentCtx = ctx
|
||||
|
|
@ -128,6 +115,27 @@ func (v *resourceView) init(ctx context.Context, ns string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (v *resourceView) filterResource(sel string) {
|
||||
v.list.SetLabelSelector(sel)
|
||||
v.refresh()
|
||||
}
|
||||
|
||||
func (v *resourceView) stopUpdates() {
|
||||
if v.cancelFn != nil {
|
||||
v.cancelFn()
|
||||
}
|
||||
}
|
||||
|
||||
func (v *resourceView) restartUpdates() {
|
||||
if v.cancelFn != nil {
|
||||
v.cancelFn()
|
||||
}
|
||||
|
||||
var vctx context.Context
|
||||
vctx, v.cancelFn = context.WithCancel(v.parentCtx)
|
||||
v.update(vctx)
|
||||
}
|
||||
|
||||
func (v *resourceView) update(ctx context.Context) {
|
||||
go func(ctx context.Context) {
|
||||
for {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/k8s"
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/gdamore/tcell"
|
||||
|
|
@ -102,6 +103,51 @@ func (v *svcView) benchStopCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
func trimCell(tv *tableView, row, col int) (string, error) {
|
||||
c := tv.GetCell(row, tv.nameColIndex()+col)
|
||||
if c == nil {
|
||||
return "", fmt.Errorf("No cell at location [%d:%d]", row, col)
|
||||
}
|
||||
return strings.TrimSpace(c.Text), nil
|
||||
}
|
||||
|
||||
func (v *svcView) checkSvc(row int) error {
|
||||
svcType, err := trimCell(v.getTV(), row, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if svcType != "NodePort" && svcType != "LoadBalancer" {
|
||||
return errors.New("You must select a reachable service")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *svcView) getExternalPort(row int) (string, error) {
|
||||
ports, err := trimCell(v.getTV(), row, 5)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pp := strings.Split(ports, " ")
|
||||
if len(pp) == 0 {
|
||||
return "", errors.New("No ports found")
|
||||
}
|
||||
|
||||
// Grap the first port pair for now...
|
||||
tokens := strings.Split(pp[0], "►")
|
||||
if len(tokens) < 2 {
|
||||
return "", errors.New("No ports pair found")
|
||||
}
|
||||
|
||||
return tokens[1], nil
|
||||
}
|
||||
|
||||
func (v *svcView) reloadBenchCfg() error {
|
||||
// BOZO!! Poorman Reload bench to make sure we pick up updates if any.
|
||||
path := benchConfig(v.app.config.K9s.CurrentCluster)
|
||||
return v.app.bench.Reload(path)
|
||||
}
|
||||
|
||||
func (v *svcView) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !v.rowSelected() {
|
||||
return evt
|
||||
|
|
@ -111,56 +157,45 @@ func (v *svcView) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
v.app.flash().err(errors.New("Only one benchmark allowed at a time"))
|
||||
return nil
|
||||
}
|
||||
|
||||
sel := v.getSelectedItem()
|
||||
tv := v.getTV()
|
||||
r, _ := tv.GetSelection()
|
||||
|
||||
// BOZO!! Poorman Reload bench to make sure we pick up updates if any.
|
||||
path := benchConfig(v.app.config.K9s.CurrentCluster)
|
||||
if err := v.app.bench.Reload(path); err != nil {
|
||||
if err := v.reloadBenchCfg(); err != nil {
|
||||
log.Error().Err(err).Msg("Bench config reload")
|
||||
v.app.flash().err(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
sel := v.getSelectedItem()
|
||||
cfg, ok := v.app.bench.Benchmarks.Services[sel]
|
||||
if !ok {
|
||||
v.app.flash().errf("No bench config found for service %s", sel)
|
||||
return nil
|
||||
}
|
||||
|
||||
svcType := strings.TrimSpace(tv.GetCell(r, tv.nameColIndex()+1).Text)
|
||||
if svcType != "NodePort" && svcType != "LoadBalancer" {
|
||||
v.app.flash().err(errors.New("You must select a reachable service"))
|
||||
return nil
|
||||
}
|
||||
|
||||
ports := strings.TrimSpace(tv.GetCell(r, tv.nameColIndex()+5).Text)
|
||||
pp := strings.Split(ports, " ")
|
||||
if len(pp) == 0 {
|
||||
v.app.flash().err(errors.New("No ports found"))
|
||||
return nil
|
||||
}
|
||||
// Grap the first port pair for now...
|
||||
tokens := strings.Split(pp[0], "►")
|
||||
if len(tokens) < 2 {
|
||||
v.app.flash().err(errors.New("No ports pair found"))
|
||||
return nil
|
||||
}
|
||||
// Found external nodeport
|
||||
port := tokens[1]
|
||||
cfg.Name = sel
|
||||
log.Debug().Msgf(">>>>> BENCHCONFIG %#v", cfg)
|
||||
|
||||
base := "http://" + cfg.Host + ":" + port + cfg.Path
|
||||
var err error
|
||||
if v.bench, err = newBenchmark(base, cfg); err != nil {
|
||||
log.Error().Err(err).Msg("Bench failed!")
|
||||
v.app.flash().errf("Bench failed %v", err)
|
||||
row, _ := v.getTV().GetSelection()
|
||||
if err := v.checkSvc(row); err != nil {
|
||||
v.app.flash().err(err)
|
||||
return nil
|
||||
}
|
||||
port, err := v.getExternalPort(row)
|
||||
if err != nil {
|
||||
v.app.flash().err(err)
|
||||
return nil
|
||||
}
|
||||
if err := v.runBenchmark(port, cfg); err != nil {
|
||||
log.Error().Err(err).Msg("Benchmark failed!")
|
||||
v.app.flash().errf("Benchmark failed %v", err)
|
||||
v.app.statusReset()
|
||||
v.bench = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *svcView) runBenchmark(port string, cfg config.BenchConfig) error {
|
||||
var err error
|
||||
base := "http://" + cfg.Host + ":" + port + cfg.Path
|
||||
if v.bench, err = newBenchmark(base, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.app.status(flashWarn, "Benchmark in progress...")
|
||||
|
|
@ -185,7 +220,6 @@ func (v *svcView) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
})
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (v *svcView) showSvcPods(ns string, sel map[string]string, b actionHandler) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -43,7 +42,7 @@ type (
|
|||
asc bool
|
||||
}
|
||||
|
||||
tableView struct {
|
||||
resTable struct {
|
||||
*tview.Table
|
||||
|
||||
app *appView
|
||||
|
|
@ -51,6 +50,11 @@ type (
|
|||
currentNS string
|
||||
data resource.TableData
|
||||
actions keyActions
|
||||
}
|
||||
|
||||
tableView struct {
|
||||
*resTable
|
||||
|
||||
cmdBuff *cmdBuff
|
||||
colorerFn colorerFn
|
||||
sortFn sortFn
|
||||
|
|
@ -62,12 +66,14 @@ type (
|
|||
|
||||
func newTableView(app *appView, title string) *tableView {
|
||||
v := tableView{
|
||||
app: app,
|
||||
Table: tview.NewTable(),
|
||||
sortCol: sortColumn{0, 0, true},
|
||||
actions: make(keyActions),
|
||||
baseTitle: title,
|
||||
cmdBuff: newCmdBuff('/'),
|
||||
resTable: &resTable{
|
||||
Table: tview.NewTable(),
|
||||
app: app,
|
||||
actions: make(keyActions),
|
||||
baseTitle: title,
|
||||
},
|
||||
sortCol: sortColumn{0, 0, true},
|
||||
cmdBuff: newCmdBuff('/'),
|
||||
}
|
||||
v.SetFixed(1, 0)
|
||||
v.SetBorder(true)
|
||||
|
|
@ -121,7 +127,7 @@ func (v *tableView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
|||
v.cmdBuff.add(evt.Rune())
|
||||
v.clearSelection()
|
||||
v.doUpdate(v.filtered())
|
||||
v.setSelection()
|
||||
v.selectFirstRow()
|
||||
return nil
|
||||
}
|
||||
key = tcell.Key(evt.Rune())
|
||||
|
|
@ -138,7 +144,7 @@ func (v *tableView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
|
||||
func (v *tableView) setSelection() {
|
||||
func (v *tableView) selectFirstRow() {
|
||||
if v.GetRowCount() > 0 {
|
||||
v.Select(1, 0)
|
||||
}
|
||||
|
|
@ -350,18 +356,6 @@ func (v *tableView) filtered() resource.TableData {
|
|||
return filtered
|
||||
}
|
||||
|
||||
func (v *tableView) sortIndicator(index int, name string) string {
|
||||
if v.sortCol.index != index {
|
||||
return name
|
||||
}
|
||||
|
||||
order := descIndicator
|
||||
if v.sortCol.asc {
|
||||
order = ascIndicator
|
||||
}
|
||||
return fmt.Sprintf("%s[%s::]%s[::]", name, v.app.styles.Style.Table.Header.SorterColor, order)
|
||||
}
|
||||
|
||||
func (v *tableView) doUpdate(data resource.TableData) {
|
||||
v.currentNS = data.Namespace
|
||||
if v.currentNS == resource.AllNamespaces && v.currentNS != "*" {
|
||||
|
|
@ -390,15 +384,18 @@ func (v *tableView) doUpdate(data resource.TableData) {
|
|||
fg := config.AsColor(v.app.styles.Style.Table.Header.FgColor)
|
||||
bg := config.AsColor(v.app.styles.Style.Table.Header.BgColor)
|
||||
for col, h := range data.Header {
|
||||
v.addHeaderCell(data.NumCols, col, h, fg, bg)
|
||||
v.addHeaderCell(data.NumCols[h], col, h)
|
||||
c := v.GetCell(0, col)
|
||||
c.SetBackgroundColor(bg)
|
||||
c.SetTextColor(fg)
|
||||
}
|
||||
row++
|
||||
|
||||
sortFn := v.defaultSort
|
||||
sortFn := defaultSort
|
||||
if v.sortFn != nil {
|
||||
sortFn = v.sortFn
|
||||
}
|
||||
prim, sec := v.sortAllRows(data.Rows, sortFn)
|
||||
prim, sec := sortAllRows(v.sortCol, data.Rows, sortFn)
|
||||
fgColor := config.AsColor(v.app.styles.Style.Table.FgColor)
|
||||
for _, pk := range prim {
|
||||
for _, sk := range sec[pk] {
|
||||
|
|
@ -421,36 +418,11 @@ func (v *tableView) doUpdate(data resource.TableData) {
|
|||
}
|
||||
}
|
||||
|
||||
func (v *tableView) sortAllRows(rows resource.RowEvents, sortFn sortFn) (resource.Row, map[string]resource.Row) {
|
||||
keys := make([]string, len(rows))
|
||||
v.sortRows(rows, sortFn, v.sortCol, keys)
|
||||
|
||||
sec := make(map[string]resource.Row, len(rows))
|
||||
for _, k := range keys {
|
||||
grp := rows[k].Fields[v.sortCol.index]
|
||||
sec[grp] = append(sec[grp], k)
|
||||
}
|
||||
|
||||
// Performs secondary to sort by name for each groups.
|
||||
prim := make(resource.Row, 0, len(sec))
|
||||
for k, v := range sec {
|
||||
sort.Strings(v)
|
||||
prim = append(prim, k)
|
||||
}
|
||||
sort.Sort(groupSorter{prim, v.sortCol.asc})
|
||||
|
||||
return prim, sec
|
||||
}
|
||||
|
||||
func (v *tableView) addHeaderCell(numCols map[string]bool, col int, name string, fg, bg tcell.Color) {
|
||||
c := tview.NewTableCell(v.sortIndicator(col, name))
|
||||
{
|
||||
c.SetExpansion(1)
|
||||
if numCols[name] || cpuRX.MatchString(name) || memRX.MatchString(name) {
|
||||
c.SetAlign(tview.AlignRight)
|
||||
}
|
||||
c.SetTextColor(fg)
|
||||
c.SetBackgroundColor(bg)
|
||||
func (v *tableView) addHeaderCell(numerical bool, col int, name string) {
|
||||
c := tview.NewTableCell(sortIndicator(v.sortCol, v.app.styles.Style, col, name))
|
||||
c.SetExpansion(1)
|
||||
if numerical || cpuRX.MatchString(name) || memRX.MatchString(name) {
|
||||
c.SetAlign(tview.AlignRight)
|
||||
}
|
||||
v.SetCell(0, col, c)
|
||||
}
|
||||
|
|
@ -475,27 +447,6 @@ func (v *tableView) formatCell(numerical bool, header, field string, padding int
|
|||
return field, align
|
||||
}
|
||||
|
||||
func (v *tableView) defaultSort(rows resource.Rows, sortCol sortColumn) {
|
||||
t := rowSorter{rows: rows, index: sortCol.index, asc: sortCol.asc}
|
||||
sort.Sort(t)
|
||||
}
|
||||
|
||||
func (*tableView) sortRows(evts resource.RowEvents, sortFn sortFn, sortCol sortColumn, keys []string) {
|
||||
rows := make(resource.Rows, 0, len(evts))
|
||||
for k, r := range evts {
|
||||
rows = append(rows, append(r.Fields, k))
|
||||
}
|
||||
sortFn(rows, sortCol)
|
||||
|
||||
for i, r := range rows {
|
||||
keys[i] = r[len(r)-1]
|
||||
}
|
||||
}
|
||||
|
||||
func (*tableView) defaultColCleanse(s string) string {
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
func (v *tableView) resetTitle() {
|
||||
var title string
|
||||
|
||||
|
|
@ -523,39 +474,3 @@ func (v *tableView) resetTitle() {
|
|||
}
|
||||
v.SetTitle(title)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Event listeners...
|
||||
|
||||
func skinTitle(fmat string, style *config.Style) string {
|
||||
fmat = strings.Replace(fmat, "[fg:bg", "["+style.Title.FgColor+":"+style.Title.BgColor, -1)
|
||||
fmat = strings.Replace(fmat, "[hilite", "["+style.Title.HighlightColor, 1)
|
||||
fmat = strings.Replace(fmat, "[key", "["+style.Menu.NumKeyColor, 1)
|
||||
fmat = strings.Replace(fmat, "[filter", "["+style.Title.FilterColor, 1)
|
||||
fmat = strings.Replace(fmat, "[count", "["+style.Title.CounterColor, 1)
|
||||
fmat = strings.Replace(fmat, ":bg:", ":"+style.Title.BgColor+":", -1)
|
||||
return fmat
|
||||
}
|
||||
|
||||
func (v *tableView) changed(s string) {}
|
||||
|
||||
func (v *tableView) active(b bool) {
|
||||
if b {
|
||||
v.SetBorderColor(tcell.ColorRed)
|
||||
return
|
||||
}
|
||||
v.SetBorderColor(tcell.ColorDodgerBlue)
|
||||
}
|
||||
|
||||
var labelCmd = regexp.MustCompile(`\A\-l`)
|
||||
|
||||
func isLabelSelector(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
return labelCmd.MatchString(s)
|
||||
}
|
||||
|
||||
func trimLabelSelector(s string) string {
|
||||
return strings.TrimSpace(s[2:])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
)
|
||||
|
||||
var labelCmd = regexp.MustCompile(`\A\-l`)
|
||||
|
||||
func isLabelSelector(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
return labelCmd.MatchString(s)
|
||||
}
|
||||
|
||||
func trimLabelSelector(s string) string {
|
||||
return strings.TrimSpace(s[2:])
|
||||
}
|
||||
|
||||
func skinTitle(fmat string, style *config.Style) string {
|
||||
fmat = strings.Replace(fmat, "[fg:bg", "["+style.Title.FgColor+":"+style.Title.BgColor, -1)
|
||||
fmat = strings.Replace(fmat, "[hilite", "["+style.Title.HighlightColor, 1)
|
||||
fmat = strings.Replace(fmat, "[key", "["+style.Menu.NumKeyColor, 1)
|
||||
fmat = strings.Replace(fmat, "[filter", "["+style.Title.FilterColor, 1)
|
||||
fmat = strings.Replace(fmat, "[count", "["+style.Title.CounterColor, 1)
|
||||
fmat = strings.Replace(fmat, ":bg:", ":"+style.Title.BgColor+":", -1)
|
||||
return fmat
|
||||
}
|
||||
|
||||
func sortRows(evts resource.RowEvents, sortFn sortFn, sortCol sortColumn, keys []string) {
|
||||
rows := make(resource.Rows, 0, len(evts))
|
||||
for k, r := range evts {
|
||||
rows = append(rows, append(r.Fields, k))
|
||||
}
|
||||
sortFn(rows, sortCol)
|
||||
|
||||
for i, r := range rows {
|
||||
keys[i] = r[len(r)-1]
|
||||
}
|
||||
}
|
||||
|
||||
func defaultSort(rows resource.Rows, sortCol sortColumn) {
|
||||
t := rowSorter{rows: rows, index: sortCol.index, asc: sortCol.asc}
|
||||
sort.Sort(t)
|
||||
}
|
||||
|
||||
func sortAllRows(col sortColumn, rows resource.RowEvents, sortFn sortFn) (resource.Row, map[string]resource.Row) {
|
||||
keys := make([]string, len(rows))
|
||||
sortRows(rows, sortFn, col, keys)
|
||||
|
||||
sec := make(map[string]resource.Row, len(rows))
|
||||
for _, k := range keys {
|
||||
grp := rows[k].Fields[col.index]
|
||||
sec[grp] = append(sec[grp], k)
|
||||
}
|
||||
|
||||
// Performs secondary to sort by name for each groups.
|
||||
prim := make(resource.Row, 0, len(sec))
|
||||
for k, v := range sec {
|
||||
sort.Strings(v)
|
||||
prim = append(prim, k)
|
||||
}
|
||||
sort.Sort(groupSorter{prim, col.asc})
|
||||
|
||||
return prim, sec
|
||||
}
|
||||
|
||||
func sortIndicator(col sortColumn, style *config.Style, index int, name string) string {
|
||||
if col.index != index {
|
||||
return name
|
||||
}
|
||||
|
||||
order := descIndicator
|
||||
if col.asc {
|
||||
order = ascIndicator
|
||||
}
|
||||
return fmt.Sprintf("%s[%s::]%s[::]", name, style.Table.Header.SorterColor, order)
|
||||
}
|
||||
|
|
@ -195,10 +195,9 @@ func TestTVSortRows(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
var v *tableView
|
||||
for _, u := range uu {
|
||||
keys := make([]string, len(u.rows))
|
||||
v.sortRows(u.rows, v.defaultSort, sortColumn{u.col, len(u.rows), u.asc}, keys)
|
||||
sortRows(u.rows, defaultSort, sortColumn{u.col, len(u.rows), u.asc}, keys)
|
||||
assert.Equal(t, u.e, keys)
|
||||
assert.Equal(t, u.first, u.rows[u.e[0]].Fields)
|
||||
}
|
||||
|
|
@ -210,12 +209,11 @@ func BenchmarkTVSortRows(b *testing.B) {
|
|||
"row2": {Fields: resource.Row{"a", "b"}},
|
||||
}
|
||||
sc := sortColumn{0, 2, true}
|
||||
var v *tableView
|
||||
keys := make([]string, len(evts))
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.sortRows(evts, v.defaultSort, sc, keys)
|
||||
sortRows(evts, defaultSort, sc, keys)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue