parent
67cdba1b30
commit
3a02001695
|
|
@ -136,6 +136,7 @@ func loadConfiguration() (*config.Config, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conn, err := client.InitConnection(k8sCfg)
|
conn, err := client.InitConnection(k8sCfg)
|
||||||
|
k9sCfg.SetConnection(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("failed to connect to cluster")
|
log.Error().Err(err).Msgf("failed to connect to cluster")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -143,12 +144,14 @@ func loadConfiguration() (*config.Config, error) {
|
||||||
if !k9sCfg.GetConnection().CheckConnectivity() {
|
if !k9sCfg.GetConnection().CheckConnectivity() {
|
||||||
log.Panic().Msgf("K9s can't connect to cluster")
|
log.Panic().Msgf("K9s can't connect to cluster")
|
||||||
}
|
}
|
||||||
|
if !k9sCfg.GetConnection().ConnectionOK() {
|
||||||
|
panic("No connectivity")
|
||||||
|
}
|
||||||
log.Info().Msg("✅ Kubernetes connectivity")
|
log.Info().Msg("✅ Kubernetes connectivity")
|
||||||
if err := k9sCfg.Save(); err != nil {
|
if err := k9sCfg.Save(); err != nil {
|
||||||
log.Error().Err(err).Msg("Config save")
|
log.Error().Err(err).Msg("Config save")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
k9sCfg.SetConnection(conn)
|
|
||||||
|
|
||||||
return k9sCfg, nil
|
return k9sCfg, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ const (
|
||||||
cacheExpiry = 5 * time.Minute
|
cacheExpiry = 5 * time.Minute
|
||||||
cacheMXKey = "metrics"
|
cacheMXKey = "metrics"
|
||||||
cacheMXAPIKey = "metricsAPI"
|
cacheMXAPIKey = "metricsAPI"
|
||||||
checkConnTimeout = 5 * time.Second
|
checkConnTimeout = 3 * time.Second
|
||||||
|
|
||||||
// CallTimeout represents default api call timeout.
|
// CallTimeout represents default api call timeout.
|
||||||
CallTimeout = 5 * time.Second
|
CallTimeout = 5 * time.Second
|
||||||
|
|
@ -64,9 +64,10 @@ func InitConnection(config *Config) (*APIClient, error) {
|
||||||
config: config,
|
config: config,
|
||||||
cache: cache.NewLRUExpireCache(cacheSize),
|
cache: cache.NewLRUExpireCache(cacheSize),
|
||||||
}
|
}
|
||||||
|
a.connOK = true
|
||||||
_, err := a.supportsMetricsResources()
|
_, err := a.supportsMetricsResources()
|
||||||
if err == nil {
|
if err != nil {
|
||||||
a.connOK = true
|
a.connOK = false
|
||||||
}
|
}
|
||||||
|
|
||||||
return &a, err
|
return &a, err
|
||||||
|
|
@ -136,7 +137,7 @@ func (a *APIClient) clearCache() {
|
||||||
func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error) {
|
func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error) {
|
||||||
log.Debug().Msgf("Check Access %q::%q", ns, gvr)
|
log.Debug().Msgf("Check Access %q::%q", ns, gvr)
|
||||||
if !a.connOK {
|
if !a.connOK {
|
||||||
return false, errors.New("no API server connection")
|
return false, errors.New("ACCESS -- No API server connection")
|
||||||
}
|
}
|
||||||
if IsClusterWide(ns) {
|
if IsClusterWide(ns) {
|
||||||
ns = AllNamespaces
|
ns = AllNamespaces
|
||||||
|
|
@ -148,6 +149,7 @@ func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debug().Msgf("----> Calling API")
|
||||||
dial, err := a.Dial()
|
dial, err := a.Dial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
@ -229,12 +231,13 @@ func (a *APIClient) CheckConnectivity() (status bool) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
cfg.Timeout = checkConnTimeout
|
||||||
client, err := kubernetes.NewForConfig(cfg)
|
client, err := kubernetes.NewForConfig(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("Unable to connect to api server")
|
log.Error().Err(err).Msgf("Unable to connect to api server")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Debug().Msgf("Checking APIServer on %#v", cfg.Host)
|
log.Debug().Msgf("CONN-CHECK on %#v", cfg.Host)
|
||||||
|
|
||||||
// Check connection
|
// Check connection
|
||||||
if _, err := client.ServerVersion(); err == nil {
|
if _, err := client.ServerVersion(); err == nil {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
|
|
@ -299,8 +298,6 @@ func (c *Config) RESTConfig() (*restclient.Config, error) {
|
||||||
}
|
}
|
||||||
c.restConfig.QPS = defaultQPS
|
c.restConfig.QPS = defaultQPS
|
||||||
c.restConfig.Burst = defaultBurst
|
c.restConfig.Burst = defaultBurst
|
||||||
c.restConfig.Timeout = checkConnTimeout
|
|
||||||
log.Debug().Msgf("Connecting to API Server %s", c.restConfig.Host)
|
|
||||||
|
|
||||||
return c.restConfig, nil
|
return c.restConfig, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ColorFmt colorize a string with ansi colors.
|
const (
|
||||||
const ColorFmt = "\x1b[%dm%s\x1b[0m"
|
colorFmt = "\x1b[%dm%s\x1b[0m"
|
||||||
|
ansiColorFmt = "\033[38;5;%dm%s\033[0m"
|
||||||
|
)
|
||||||
|
|
||||||
// Paint describes a terminal color.
|
// Paint describes a terminal color.
|
||||||
type Paint int
|
type Paint int
|
||||||
|
|
@ -30,5 +32,29 @@ func Colorize(s string, c Paint) string {
|
||||||
if c == 0 {
|
if c == 0 {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(ColorFmt, c, s)
|
return fmt.Sprintf(colorFmt, c, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnsiColorize colors a string.
|
||||||
|
func AnsiColorize(s string, c int) string {
|
||||||
|
return fmt.Sprintf(ansiColorFmt, c, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight colorize bytes at given indices.
|
||||||
|
func Highlight(bb []byte, ii []int, c int) []byte {
|
||||||
|
b := make([]byte, 0, len(bb))
|
||||||
|
for i, j := 0, 0; i < len(bb); i++ {
|
||||||
|
if j < len(ii) && ii[j] == i {
|
||||||
|
b = append(b, colorizeByte(bb[i], 209)...)
|
||||||
|
j++
|
||||||
|
} else {
|
||||||
|
b = append(b, bb[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorizeByte(b byte, color int) []byte {
|
||||||
|
return []byte(AnsiColorize(string(b), color))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,3 +25,26 @@ func TestColorize(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHighlight(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
text []byte
|
||||||
|
indices []int
|
||||||
|
color int
|
||||||
|
e string
|
||||||
|
}{
|
||||||
|
"white": {
|
||||||
|
text: []byte("the brown fox"),
|
||||||
|
color: 209,
|
||||||
|
indices: []int{4, 5, 6, 7, 8},
|
||||||
|
e: "the \x1b[38;5;209mb\x1b[0m\x1b[38;5;209mr\x1b[0m\x1b[38;5;209mo\x1b[0m\x1b[38;5;209mw\x1b[0m\x1b[38;5;209mn\x1b[0m fox",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
assert.Equal(t, u.e, string(color.Highlight([]byte(u.text), u.indices, u.color)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/color"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/sahilm/fuzzy"
|
"github.com/sahilm/fuzzy"
|
||||||
|
|
@ -51,6 +52,18 @@ func (l *LogItem) ID() string {
|
||||||
return l.Container
|
return l.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *LogItem) Clone() *LogItem {
|
||||||
|
bytes := make([]byte, len(l.Bytes))
|
||||||
|
copy(bytes, l.Bytes)
|
||||||
|
return &LogItem{
|
||||||
|
Container: l.Container,
|
||||||
|
Pod: l.Pod,
|
||||||
|
Timestamp: l.Timestamp,
|
||||||
|
SingleContainer: l.SingleContainer,
|
||||||
|
Bytes: bytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Info returns pod and container information.
|
// Info returns pod and container information.
|
||||||
func (l *LogItem) Info() string {
|
func (l *LogItem) Info() string {
|
||||||
return fmt.Sprintf("%q::%q", l.Pod, l.Container)
|
return fmt.Sprintf("%q::%q", l.Pod, l.Container)
|
||||||
|
|
@ -61,26 +74,19 @@ func (l *LogItem) IsEmpty() bool {
|
||||||
return len(l.Bytes) == 0
|
return len(l.Bytes) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const colorFmt = "\033[38;5;%dm%s\033[0m"
|
|
||||||
|
|
||||||
// colorize me
|
|
||||||
func colorize(s string, c int) string {
|
|
||||||
return fmt.Sprintf(colorFmt, c, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render returns a log line as string.
|
// Render returns a log line as string.
|
||||||
func (l *LogItem) Render(c int, showTime bool) []byte {
|
func (l *LogItem) Render(c int, showTime bool) []byte {
|
||||||
bb := make([]byte, 0, 30+len(l.Bytes)+len(l.Info()))
|
bb := make([]byte, 0, 30+len(l.Bytes)+len(l.Info()))
|
||||||
if showTime {
|
if showTime {
|
||||||
bb = append(bb, colorize(fmt.Sprintf("%-30s ", l.Timestamp), 106)...)
|
bb = append(bb, color.AnsiColorize(fmt.Sprintf("%-30s ", l.Timestamp), 106)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.Pod != "" {
|
if l.Pod != "" {
|
||||||
bb = append(bb, []byte(colorize(l.Pod, c))...)
|
bb = append(bb, []byte(color.AnsiColorize(l.Pod, c))...)
|
||||||
bb = append(bb, ':')
|
bb = append(bb, ':')
|
||||||
}
|
}
|
||||||
if !l.SingleContainer && l.Container != "" {
|
if !l.SingleContainer && l.Container != "" {
|
||||||
bb = append(bb, []byte(colorize(l.Container, c))...)
|
bb = append(bb, []byte(color.AnsiColorize(l.Container, c))...)
|
||||||
bb = append(bb, ' ')
|
bb = append(bb, ' ')
|
||||||
}
|
}
|
||||||
bb = append(bb, []byte(tview.Escape(string(l.Bytes)))...)
|
bb = append(bb, []byte(tview.Escape(string(l.Bytes)))...)
|
||||||
|
|
@ -139,45 +145,54 @@ func (l LogItems) DumpDebug(m string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter filters out log items based on given filter.
|
// Filter filters out log items based on given filter.
|
||||||
func (l LogItems) Filter(q string) ([]int, error) {
|
func (l LogItems) Filter(q string) ([]int, [][]int, error) {
|
||||||
if q == "" {
|
if q == "" {
|
||||||
return nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
if IsFuzzySelector(q) {
|
if IsFuzzySelector(q) {
|
||||||
return l.fuzzyFilter(strings.TrimSpace(q[2:])), nil
|
mm, ii := l.fuzzyFilter(strings.TrimSpace(q[2:]))
|
||||||
|
return mm, ii, nil
|
||||||
}
|
}
|
||||||
indexes, err := l.filterLogs(q)
|
matches, indices, err := l.filterLogs(q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("Logs filter failed")
|
log.Error().Err(err).Msgf("Logs filter failed")
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return indexes, nil
|
return matches, indices, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var fuzzyRx = regexp.MustCompile(`\A\-f`)
|
var fuzzyRx = regexp.MustCompile(`\A\-f`)
|
||||||
|
|
||||||
func (l LogItems) fuzzyFilter(q string) []int {
|
func (l LogItems) fuzzyFilter(q string) ([]int, [][]int) {
|
||||||
q = strings.TrimSpace(q)
|
q = strings.TrimSpace(q)
|
||||||
matches := make([]int, 0, len(l))
|
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
|
||||||
mm := fuzzy.Find(q, l.Lines())
|
mm := fuzzy.Find(q, l.Lines())
|
||||||
for _, m := range mm {
|
for _, m := range mm {
|
||||||
matches = append(matches, m.Index)
|
matches = append(matches, m.Index)
|
||||||
|
indices = append(indices, m.MatchedIndexes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches
|
return matches, indices
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l LogItems) filterLogs(q string) ([]int, error) {
|
func (l LogItems) filterLogs(q string) ([]int, [][]int, error) {
|
||||||
rx, err := regexp.Compile(`(?i)` + q)
|
rx, err := regexp.Compile(`(?i)` + q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
matches := make([]int, 0, len(l))
|
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
|
||||||
for i, line := range l.Lines() {
|
for i, line := range l.Lines() {
|
||||||
if rx.MatchString(line) {
|
if locs := rx.FindStringIndex(line); locs != nil {
|
||||||
matches = append(matches, i)
|
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, nil
|
return matches, indices, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ func TestLogItemsFilter(t *testing.T) {
|
||||||
for _, i := range ii {
|
for _, i := range ii {
|
||||||
i.Pod, i.Container = n, u.opts.Container
|
i.Pod, i.Container = n, u.opts.Container
|
||||||
}
|
}
|
||||||
res, err := ii.Filter(u.q)
|
res, _, err := ii.Filter(u.q)
|
||||||
assert.Equal(t, u.err, err)
|
assert.Equal(t, u.err, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
assert.Equal(t, u.e, res)
|
assert.Equal(t, u.e, res)
|
||||||
|
|
|
||||||
|
|
@ -275,7 +275,7 @@ func loadRBAC(m ResourceMetas) {
|
||||||
|
|
||||||
func loadPreferred(f Factory, m ResourceMetas) error {
|
func loadPreferred(f Factory, m ResourceMetas) error {
|
||||||
if !f.Client().ConnectionOK() {
|
if !f.Client().ConnectionOK() {
|
||||||
log.Error().Msgf("no API server connection")
|
log.Error().Msgf("PreferredRES - No API server connection")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
|
"github.com/derailed/k9s/internal/color"
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/k9s/internal/dao"
|
"github.com/derailed/k9s/internal/dao"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
@ -277,21 +278,24 @@ func applyFilter(q string, lines dao.LogItems) (dao.LogItems, error) {
|
||||||
if q == "" {
|
if q == "" {
|
||||||
return lines, nil
|
return lines, nil
|
||||||
}
|
}
|
||||||
indexes, err := lines.Filter(q)
|
matches, indices, err := lines.Filter(q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// No filter!
|
// No filter!
|
||||||
if indexes == nil {
|
if matches == nil {
|
||||||
return lines, nil
|
return lines, nil
|
||||||
}
|
}
|
||||||
// Blank filter
|
// Blank filter
|
||||||
if len(indexes) == 0 {
|
if len(matches) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
filtered := make(dao.LogItems, 0, len(indexes))
|
filtered := make(dao.LogItems, 0, len(matches))
|
||||||
for _, idx := range indexes {
|
for i, idx := range matches {
|
||||||
filtered = append(filtered, lines[idx])
|
item := lines[idx].Clone()
|
||||||
|
item.Bytes = color.Highlight(item.Bytes, indices[i], 209)
|
||||||
|
filtered = append(filtered, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return filtered, nil
|
return filtered, nil
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,8 @@ func TestLogTimedout(t *testing.T) {
|
||||||
assert.Equal(t, 1, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
assert.Equal(t, 2, v.clearCalled)
|
assert.Equal(t, 2, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
assert.Equal(t, dao.LogItems{data[0]}, v.data)
|
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].Bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package ui
|
||||||
import (
|
import (
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SelectTable represents a table with selections.
|
// SelectTable represents a table with selections.
|
||||||
|
|
@ -51,6 +52,17 @@ func (s *SelectTable) GetSelectedItems() []string {
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRowID returns the row id at at given location.
|
||||||
|
func (s *SelectTable) GetRowID(index int) (string, bool) {
|
||||||
|
cell := s.GetCell(index, 0)
|
||||||
|
if cell == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
id, ok := cell.GetReference().(string)
|
||||||
|
|
||||||
|
return id, ok
|
||||||
|
}
|
||||||
|
|
||||||
// GetSelectedItem returns the currently selected item name.
|
// GetSelectedItem returns the currently selected item name.
|
||||||
func (s *SelectTable) GetSelectedItem() string {
|
func (s *SelectTable) GetSelectedItem() string {
|
||||||
if s.GetSelectedRowIndex() == 0 || s.model.Empty() {
|
if s.GetSelectedRowIndex() == 0 || s.model.Empty() {
|
||||||
|
|
@ -138,6 +150,68 @@ func (s *SelectTable) ToggleMark() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToggleSpanMark toggles marked row
|
||||||
|
func (s *SelectTable) SpanMark() {
|
||||||
|
selIndex, prev := s.GetSelectedRowIndex(), -1
|
||||||
|
if selIndex <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Look back to find previous mark
|
||||||
|
for i := selIndex - 1; i > 0; i-- {
|
||||||
|
id, ok := s.GetRowID(i)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, ok := s.marks[id]; ok {
|
||||||
|
prev = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if prev != -1 {
|
||||||
|
s.markRange(prev, selIndex)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look forward to see if we have a mark
|
||||||
|
for i := selIndex; i < s.GetRowCount(); i++ {
|
||||||
|
id, ok := s.GetRowID(i)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if _, ok := s.marks[id]; ok {
|
||||||
|
prev = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.markRange(prev, selIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SelectTable) markRange(prev, curr int) {
|
||||||
|
if prev < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if prev > curr {
|
||||||
|
prev, curr = curr, prev
|
||||||
|
}
|
||||||
|
log.Debug().Msgf("Span Range %d::%d", prev, curr)
|
||||||
|
for i := prev + 1; i <= curr; i++ {
|
||||||
|
id, ok := s.GetRowID(i)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s.marks[id] = struct{}{}
|
||||||
|
cell := s.GetCell(s.GetSelectedRowIndex(), 0)
|
||||||
|
if cell == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s.SetSelectedStyle(
|
||||||
|
tcell.ColorBlack,
|
||||||
|
cell.Color,
|
||||||
|
tcell.AttrBold,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// IsMarked returns true if this item was marked.
|
// IsMarked returns true if this item was marked.
|
||||||
func (s *Table) IsMarked(item string) bool {
|
func (s *Table) IsMarked(item string) bool {
|
||||||
_, ok := s.marks[item]
|
_, ok := s.marks[item]
|
||||||
|
|
|
||||||
|
|
@ -399,13 +399,9 @@ func (b *Browser) defaultContext() context.Context {
|
||||||
if b.Path != "" {
|
if b.Path != "" {
|
||||||
ctx = context.WithValue(ctx, internal.KeyPath, b.Path)
|
ctx = context.WithValue(ctx, internal.KeyPath, b.Path)
|
||||||
}
|
}
|
||||||
// BOZO!!
|
|
||||||
// ctx = context.WithValue(ctx, internal.KeyLabels, "")
|
|
||||||
if ui.IsLabelSelector(b.CmdBuff().GetText()) {
|
if ui.IsLabelSelector(b.CmdBuff().GetText()) {
|
||||||
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(b.CmdBuff().GetText()))
|
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(b.CmdBuff().GetText()))
|
||||||
}
|
}
|
||||||
// BOZO!!
|
|
||||||
// ctx = context.WithValue(ctx, internal.KeyFields, "")
|
|
||||||
ctx = context.WithValue(ctx, internal.KeyNamespace, client.CleanseNamespace(b.App().Config.ActiveNamespace()))
|
ctx = context.WithValue(ctx, internal.KeyNamespace, client.CleanseNamespace(b.App().Config.ActiveNamespace()))
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ func (c *Command) Init() error {
|
||||||
c.alias = dao.NewAlias(c.app.factory)
|
c.alias = dao.NewAlias(c.app.factory)
|
||||||
if _, err := c.alias.Ensure(); err != nil {
|
if _, err := c.alias.Ensure(); err != nil {
|
||||||
log.Error().Err(err).Msgf("command init failed!")
|
log.Error().Err(err).Msgf("command init failed!")
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
customViewers = loadCustomViewers()
|
customViewers = loadCustomViewers()
|
||||||
|
|
||||||
|
|
@ -106,21 +107,6 @@ func (c *Command) xrayCmd(cmd string) error {
|
||||||
return c.exec(cmd, "xrays", x, true)
|
return c.exec(cmd, "xrays", x, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BOZO!!
|
|
||||||
// func (c *Command) checkAccess(gvr string) error {
|
|
||||||
// m, err := dao.MetaAccess.MetaFor(client.NewGVR(gvr))
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// ns := client.CleanseNamespace(c.app.Config.ActiveNamespace())
|
|
||||||
// if dao.IsK8sMeta(m) && c.app.ConOK() {
|
|
||||||
// if _, e := c.app.factory.CanForResource(ns, gvr, client.MonitorAccess); e != nil {
|
|
||||||
// return e
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Exec the Command by showing associated display.
|
// Exec the Command by showing associated display.
|
||||||
func (c *Command) run(cmd, path string, clearStack bool) error {
|
func (c *Command) run(cmd, path string, clearStack bool) error {
|
||||||
if c.specialCmd(cmd, path) {
|
if c.specialCmd(cmd, path) {
|
||||||
|
|
@ -131,9 +117,6 @@ func (c *Command) run(cmd, path string, clearStack bool) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
//if err := c.checkAccess(gvr); err != nil {
|
|
||||||
// return err
|
|
||||||
//}
|
|
||||||
|
|
||||||
switch cmds[0] {
|
switch cmds[0] {
|
||||||
case "ctx", "context", "contexts":
|
case "ctx", "context", "contexts":
|
||||||
|
|
@ -159,6 +142,7 @@ func (c *Command) run(cmd, path string, clearStack bool) error {
|
||||||
|
|
||||||
func (c *Command) defaultCmd() error {
|
func (c *Command) defaultCmd() error {
|
||||||
if !c.app.Conn().ConnectionOK() {
|
if !c.app.Conn().ConnectionOK() {
|
||||||
|
log.Debug().Msgf("YO!!")
|
||||||
return c.run("ctx", "", true)
|
return c.run("ctx", "", true)
|
||||||
}
|
}
|
||||||
view := c.app.Config.ActiveView()
|
view := c.app.Config.ActiveView()
|
||||||
|
|
|
||||||
|
|
@ -220,11 +220,11 @@ func (h *Help) showGeneral() model.MenuHints {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Mnemonic: "tab",
|
Mnemonic: "tab",
|
||||||
Description: "Next Field",
|
Description: "Field Next",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Mnemonic: "backtab",
|
Mnemonic: "backtab",
|
||||||
Description: "Previous Field",
|
Description: "Field Previous",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Mnemonic: "Ctrl-r",
|
Mnemonic: "Ctrl-r",
|
||||||
|
|
@ -232,7 +232,7 @@ func (h *Help) showGeneral() model.MenuHints {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Mnemonic: "Ctrl-u",
|
Mnemonic: "Ctrl-u",
|
||||||
Description: "Clear command",
|
Description: "Command Clear",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Mnemonic: "Ctrl-e",
|
Mnemonic: "Ctrl-e",
|
||||||
|
|
@ -248,7 +248,11 @@ func (h *Help) showGeneral() model.MenuHints {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Mnemonic: "Ctrl-space",
|
Mnemonic: "Ctrl-space",
|
||||||
Description: "Clear Marks",
|
Description: "Mark Range",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Mnemonic: "Ctrl-\\",
|
||||||
|
Description: "Mark Clear",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Mnemonic: "Ctrl-s",
|
Mnemonic: "Ctrl-s",
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,7 @@ func (l *Log) bindKeys() {
|
||||||
ui.Key5: ui.NewKeyAction("1h", l.sinceCmd(60*60), true),
|
ui.Key5: ui.NewKeyAction("1h", l.sinceCmd(60*60), true),
|
||||||
tcell.KeyEnter: ui.NewSharedKeyAction("Filter", l.filterCmd, false),
|
tcell.KeyEnter: ui.NewSharedKeyAction("Filter", l.filterCmd, false),
|
||||||
ui.KeyC: ui.NewKeyAction("Clear", l.clearCmd, true),
|
ui.KeyC: ui.NewKeyAction("Clear", l.clearCmd, true),
|
||||||
|
ui.KeyM: ui.NewKeyAction("Mark", l.markCmd, true),
|
||||||
ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.toggleAutoScrollCmd, true),
|
ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.toggleAutoScrollCmd, true),
|
||||||
ui.KeyF: ui.NewKeyAction("Toggle FullScreen", l.toggleFullScreenCmd, true),
|
ui.KeyF: ui.NewKeyAction("Toggle FullScreen", l.toggleFullScreenCmd, true),
|
||||||
ui.KeyT: ui.NewKeyAction("Toggle Timestamp", l.toggleTimestampCmd, true),
|
ui.KeyT: ui.NewKeyAction("Toggle Timestamp", l.toggleTimestampCmd, true),
|
||||||
|
|
@ -324,6 +325,11 @@ func (l *Log) clearCmd(*tcell.EventKey) *tcell.EventKey {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Log) markCmd(*tcell.EventKey) *tcell.EventKey {
|
||||||
|
fmt.Fprintln(l.ansiWriter, fmt.Sprintf("[white::b]%s[::]", strings.Repeat("-", 80)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Log) toggleTimestampCmd(evt *tcell.EventKey) *tcell.EventKey {
|
func (l *Log) toggleTimestampCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if l.app.InCmdMode() {
|
if l.app.InCmdMode() {
|
||||||
return evt
|
return evt
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ func TestLogAutoScroll(t *testing.T) {
|
||||||
v.GetModel().Set(dao.LogItems{dao.NewLogItemFromString("blee"), dao.NewLogItemFromString("bozo")})
|
v.GetModel().Set(dao.LogItems{dao.NewLogItemFromString("blee"), dao.NewLogItemFromString("bozo")})
|
||||||
v.GetModel().Notify(true)
|
v.GetModel().Notify(true)
|
||||||
|
|
||||||
assert.Equal(t, 13, len(v.Hints()))
|
assert.Equal(t, 14, len(v.Hints()))
|
||||||
|
|
||||||
v.toggleAutoScrollCmd(nil)
|
v.toggleAutoScrollCmd(nil)
|
||||||
assert.Equal(t, "Autoscroll: Off FullScreen: Off Timestamps: Off Wrap: Off", v.Indicator().GetText(true))
|
assert.Equal(t, "Autoscroll: Off FullScreen: Off Timestamps: Off Wrap: Off", v.Indicator().GetText(true))
|
||||||
|
|
@ -85,7 +85,7 @@ func TestLogFilter(t *testing.T) {
|
||||||
l.SendKeys(ui.KeySlash)
|
l.SendKeys(ui.KeySlash)
|
||||||
l.SendStrokes("zorg")
|
l.SendStrokes("zorg")
|
||||||
|
|
||||||
assert.Equal(t, "zorg", list.lines)
|
assert.Equal(t, "\x1b[38;5;209mz\x1b[0m\x1b[38;5;209mo\x1b[0m\x1b[38;5;209mr\x1b[0m\x1b[38;5;209mg\x1b[0m", list.lines)
|
||||||
assert.Equal(t, 5, list.change)
|
assert.Equal(t, 5, list.change)
|
||||||
assert.Equal(t, 5, list.clear)
|
assert.Equal(t, 5, list.clear)
|
||||||
assert.Equal(t, 0, list.fail)
|
assert.Equal(t, 0, list.fail)
|
||||||
|
|
|
||||||
|
|
@ -150,14 +150,15 @@ func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
|
||||||
func (t *Table) bindKeys() {
|
func (t *Table) bindKeys() {
|
||||||
t.Actions().Add(ui.KeyActions{
|
t.Actions().Add(ui.KeyActions{
|
||||||
ui.KeySpace: ui.NewSharedKeyAction("Mark", t.markCmd, false),
|
ui.KeySpace: ui.NewSharedKeyAction("Mark", t.markCmd, false),
|
||||||
tcell.KeyCtrlSpace: ui.NewSharedKeyAction("Marks Clear", t.clearMarksCmd, false),
|
tcell.KeyCtrlSpace: ui.NewSharedKeyAction("Mark Range", t.markSpanCmd, false),
|
||||||
tcell.KeyCtrlS: ui.NewSharedKeyAction("Save", t.saveCmd, false),
|
tcell.KeyCtrlBackslash: ui.NewSharedKeyAction("Marks Clear", t.clearMarksCmd, false),
|
||||||
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", t.activateCmd, false),
|
tcell.KeyCtrlS: ui.NewSharedKeyAction("Save", t.saveCmd, false),
|
||||||
tcell.KeyCtrlZ: ui.NewKeyAction("Toggle Faults", t.toggleFaultCmd, false),
|
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", t.activateCmd, false),
|
||||||
tcell.KeyCtrlW: ui.NewKeyAction("Toggle Wide", t.toggleWideCmd, false),
|
tcell.KeyCtrlZ: ui.NewKeyAction("Toggle Faults", t.toggleFaultCmd, false),
|
||||||
ui.KeyShiftN: ui.NewKeyAction("Sort Name", t.SortColCmd(nameCol, true), false),
|
tcell.KeyCtrlW: ui.NewKeyAction("Toggle Wide", t.toggleWideCmd, false),
|
||||||
ui.KeyShiftA: ui.NewKeyAction("Sort Age", t.SortColCmd(ageCol, true), false),
|
ui.KeyShiftN: ui.NewKeyAction("Sort Name", t.SortColCmd(nameCol, true), false),
|
||||||
|
ui.KeyShiftA: ui.NewKeyAction("Sort Age", t.SortColCmd(ageCol, true), false),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,22 +189,22 @@ func (t *Table) cpCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) markCmd(evt *tcell.EventKey) *tcell.EventKey {
|
func (t *Table) markCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
path := t.GetSelectedItem()
|
|
||||||
if path == "" {
|
|
||||||
return evt
|
|
||||||
}
|
|
||||||
t.ToggleMark()
|
t.ToggleMark()
|
||||||
t.Refresh()
|
t.Refresh()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Table) markSpanCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
t.SpanMark()
|
||||||
|
t.Refresh()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Table) clearMarksCmd(evt *tcell.EventKey) *tcell.EventKey {
|
func (t *Table) clearMarksCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
path := t.GetSelectedItem()
|
|
||||||
if path == "" {
|
|
||||||
return evt
|
|
||||||
}
|
|
||||||
t.ClearMarks()
|
t.ClearMarks()
|
||||||
|
t.Refresh()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue