add rx filter + headless
parent
7d19faf55a
commit
c34994bcbf
38
cmd/root.go
38
cmd/root.go
|
|
@ -17,17 +17,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
appName = "k9s"
|
appName = "k9s"
|
||||||
defaultRefreshRate = 2 // secs
|
shortAppDesc = "A graphical CLI for your Kubernetes cluster management."
|
||||||
defaultLogLevel = "info"
|
longAppDesc = "K9s is a CLI to view and manage your Kubernetes clusters."
|
||||||
shortAppDesc = "A graphical CLI for your Kubernetes cluster management."
|
|
||||||
longAppDesc = "K9s is a CLI to view and manage your Kubernetes clusters."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version, commit, date = "dev", "dev", "n/a"
|
version, commit, date = "dev", "dev", "n/a"
|
||||||
refreshRate int
|
k9sFlags *config.Flags
|
||||||
logLevel string
|
|
||||||
k8sFlags *genericclioptions.ConfigFlags
|
k8sFlags *genericclioptions.ConfigFlags
|
||||||
|
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
|
|
@ -71,12 +68,12 @@ func run(cmd *cobra.Command, args []string) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
zerolog.SetGlobalLevel(parseLevel(logLevel))
|
zerolog.SetGlobalLevel(parseLevel(*k9sFlags.LogLevel))
|
||||||
cfg := loadConfiguration()
|
cfg := loadConfiguration()
|
||||||
app := views.NewApp(cfg)
|
app := views.NewApp(cfg)
|
||||||
{
|
{
|
||||||
defer app.BailOut()
|
defer app.BailOut()
|
||||||
app.Init(version, refreshRate)
|
app.Init(version, *k9sFlags.RefreshRate)
|
||||||
app.Run()
|
app.Run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,8 +88,12 @@ func loadConfiguration() *config.Config {
|
||||||
log.Warn().Msg("Unable to locate K9s config. Generating new configuration...")
|
log.Warn().Msg("Unable to locate K9s config. Generating new configuration...")
|
||||||
}
|
}
|
||||||
|
|
||||||
if refreshRate != defaultRefreshRate {
|
if *k9sFlags.RefreshRate != config.DefaultRefreshRate {
|
||||||
k9sCfg.K9s.OverrideRefreshRate(refreshRate)
|
k9sCfg.K9s.OverrideRefreshRate(*k9sFlags.RefreshRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if k9sFlags.Headless != nil {
|
||||||
|
k9sCfg.K9s.OverrideHeadless(*k9sFlags.Headless)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := k9sCfg.Refine(k8sFlags); err != nil {
|
if err := k9sCfg.Refine(k8sFlags); err != nil {
|
||||||
|
|
@ -126,18 +127,25 @@ func parseLevel(level string) zerolog.Level {
|
||||||
}
|
}
|
||||||
|
|
||||||
func initK9sFlags() {
|
func initK9sFlags() {
|
||||||
|
k9sFlags = config.NewFlags()
|
||||||
rootCmd.Flags().IntVarP(
|
rootCmd.Flags().IntVarP(
|
||||||
&refreshRate,
|
k9sFlags.RefreshRate,
|
||||||
"refresh", "r",
|
"refresh", "r",
|
||||||
defaultRefreshRate,
|
config.DefaultRefreshRate,
|
||||||
"Specifies the default refresh rate as an integer (sec)",
|
"Specifies the default refresh rate as an integer (sec)",
|
||||||
)
|
)
|
||||||
rootCmd.Flags().StringVarP(
|
rootCmd.Flags().StringVarP(
|
||||||
&logLevel,
|
k9sFlags.LogLevel,
|
||||||
"logLevel", "l",
|
"logLevel", "l",
|
||||||
defaultLogLevel,
|
config.DefaultLogLevel,
|
||||||
"Specify a log level (info, warn, debug, error, fatal, panic, trace)",
|
"Specify a log level (info, warn, debug, error, fatal, panic, trace)",
|
||||||
)
|
)
|
||||||
|
rootCmd.Flags().BoolVar(
|
||||||
|
k9sFlags.Headless,
|
||||||
|
"headless",
|
||||||
|
false,
|
||||||
|
"Turn K9s header off",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initK8sFlags() {
|
func initK8sFlags() {
|
||||||
|
|
|
||||||
1
go.sum
1
go.sum
|
|
@ -380,6 +380,7 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
|
||||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/genproto v0.0.0-20170731182057-09f6ed296fc6/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20170731182057-09f6ed296fc6/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/grpc v1.13.0 h1:bHIbVsCwmvbArgCJmLdgOdHFXlKqTOVjbibbS19cXHc=
|
||||||
google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,7 @@ func (c *Config) Load(path string) error {
|
||||||
if cfg.K9s != nil {
|
if cfg.K9s != nil {
|
||||||
c.K9s = cfg.K9s
|
c.K9s = cfg.K9s
|
||||||
}
|
}
|
||||||
|
log.Debug().Msgf("Headless ? %t", c.K9s.Headless)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,7 @@ func TestSetup(t *testing.T) {
|
||||||
|
|
||||||
var expectedConfig = `k9s:
|
var expectedConfig = `k9s:
|
||||||
refreshRate: 100
|
refreshRate: 100
|
||||||
|
headless: false
|
||||||
logBufferSize: 500
|
logBufferSize: 500
|
||||||
logRequestSize: 100
|
logRequestSize: 100
|
||||||
currentContext: blee
|
currentContext: blee
|
||||||
|
|
@ -310,6 +311,7 @@ var expectedConfig = `k9s:
|
||||||
|
|
||||||
var resetConfig = `k9s:
|
var resetConfig = `k9s:
|
||||||
refreshRate: 2
|
refreshRate: 2
|
||||||
|
headless: false
|
||||||
logBufferSize: 200
|
logBufferSize: 200
|
||||||
logRequestSize: 200
|
logRequestSize: 200
|
||||||
currentContext: blee
|
currentContext: blee
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultRefreshRate represents the refresh interval.
|
||||||
|
DefaultRefreshRate = 2 // secs
|
||||||
|
// DefaultLogLevel represents the default log level.
|
||||||
|
DefaultLogLevel = "info"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Flags represents K9s configuration flags.
|
||||||
|
type Flags struct {
|
||||||
|
RefreshRate *int
|
||||||
|
LogLevel *string
|
||||||
|
Headless *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFlags returns new configuration flags.
|
||||||
|
func NewFlags() *Flags {
|
||||||
|
return &Flags{
|
||||||
|
RefreshRate: intPtr(DefaultRefreshRate),
|
||||||
|
LogLevel: strPtr(DefaultLogLevel),
|
||||||
|
Headless: boolPtr(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolPtr(b bool) *bool {
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
|
func intPtr(i int) *int {
|
||||||
|
return &i
|
||||||
|
}
|
||||||
|
|
||||||
|
func strPtr(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
@ -12,14 +12,16 @@ const (
|
||||||
|
|
||||||
// K9s tracks K9s configuration options.
|
// K9s tracks K9s configuration options.
|
||||||
type K9s struct {
|
type K9s struct {
|
||||||
RefreshRate int `yaml:"refreshRate"`
|
RefreshRate int `yaml:"refreshRate"`
|
||||||
manualRefreshRate int
|
Headless bool `yaml:"headless"`
|
||||||
LogBufferSize int `yaml:"logBufferSize"`
|
LogBufferSize int `yaml:"logBufferSize"`
|
||||||
LogRequestSize int `yaml:"logRequestSize"`
|
LogRequestSize int `yaml:"logRequestSize"`
|
||||||
CurrentContext string `yaml:"currentContext"`
|
CurrentContext string `yaml:"currentContext"`
|
||||||
CurrentCluster string `yaml:"currentCluster"`
|
CurrentCluster string `yaml:"currentCluster"`
|
||||||
Clusters map[string]*Cluster `yaml:"clusters,omitempty"`
|
Clusters map[string]*Cluster `yaml:"clusters,omitempty"`
|
||||||
Plugins map[string]*Plugin `yaml:"plugins,omitempty"`
|
Plugins map[string]*Plugin `yaml:"plugins,omitempty"`
|
||||||
|
manualRefreshRate int
|
||||||
|
manualHeadless *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewK9s create a new K9s configuration.
|
// NewK9s create a new K9s configuration.
|
||||||
|
|
@ -38,6 +40,21 @@ func (k *K9s) OverrideRefreshRate(r int) {
|
||||||
k.manualRefreshRate = r
|
k.manualRefreshRate = r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OverrideHeadless set the headlessness manually.
|
||||||
|
func (k *K9s) OverrideHeadless(b bool) {
|
||||||
|
k.manualHeadless = &b
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeadless returns headless setting.
|
||||||
|
func (k *K9s) GetHeadless() bool {
|
||||||
|
h := k.Headless
|
||||||
|
if k.manualHeadless != nil && *k.manualHeadless {
|
||||||
|
h = *k.manualHeadless
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
// GetRefreshRate returns the current refresh rate.
|
// GetRefreshRate returns the current refresh rate.
|
||||||
func (k *K9s) GetRefreshRate() int {
|
func (k *K9s) GetRefreshRate() int {
|
||||||
rate := k.RefreshRate
|
rate := k.RefreshRate
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ func (b *Benchmark) init(base string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
log.Debug().Msgf("Benchmarking Request %s", req.URL.String())
|
||||||
|
|
||||||
if b.config.Auth.User != "" || b.config.Auth.Password != "" {
|
if b.config.Auth.User != "" || b.config.Auth.Password != "" {
|
||||||
req.SetBasicAuth(b.config.Auth.User, b.config.Auth.Password)
|
req.SetBasicAuth(b.config.Auth.User, b.config.Auth.Password)
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ func (a KeyActions) Hints() Hints {
|
||||||
for _, k := range kk {
|
for _, k := range kk {
|
||||||
if name, ok := tcell.KeyNames[tcell.Key(k)]; ok {
|
if name, ok := tcell.KeyNames[tcell.Key(k)]; ok {
|
||||||
hh = append(hh, Hint{
|
hh = append(hh, Hint{
|
||||||
mnemonic: name,
|
Mnemonic: name,
|
||||||
description: a[tcell.Key(k)].Description})
|
Description: a[tcell.Key(k)].Description})
|
||||||
} else {
|
} else {
|
||||||
log.Error().Msgf("Unable to locate KeyName for %#v", string(k))
|
log.Error().Msgf("Unable to locate KeyName for %#v", string(k))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ type (
|
||||||
content *tview.Pages
|
content *tview.Pages
|
||||||
views map[string]tview.Primitive
|
views map[string]tview.Primitive
|
||||||
cmdBuff *CmdBuff
|
cmdBuff *CmdBuff
|
||||||
|
hints Hints
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -224,9 +225,15 @@ func (a *App) ActiveView() Igniter {
|
||||||
|
|
||||||
// SetHints updates menu hints.
|
// SetHints updates menu hints.
|
||||||
func (a *App) SetHints(h Hints) {
|
func (a *App) SetHints(h Hints) {
|
||||||
|
a.hints = h
|
||||||
a.views["menu"].(*MenuView).HydrateMenu(h)
|
a.views["menu"].(*MenuView).HydrateMenu(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHints retrieves the currently active hints.
|
||||||
|
func (a *App) GetHints() Hints {
|
||||||
|
return a.hints
|
||||||
|
}
|
||||||
|
|
||||||
// StatusReset reset log back to normal.
|
// StatusReset reset log back to normal.
|
||||||
func (a *App) StatusReset() {
|
func (a *App) StatusReset() {
|
||||||
a.Logo().Reset()
|
a.Logo().Reset()
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
type (
|
type (
|
||||||
// Hint represents keyboard mnemonic.
|
// Hint represents keyboard mnemonic.
|
||||||
Hint struct {
|
Hint struct {
|
||||||
mnemonic, description string
|
Mnemonic, Description string
|
||||||
}
|
}
|
||||||
// Hints a collection of keyboard mnemonics.
|
// Hints a collection of keyboard mnemonics.
|
||||||
Hints []Hint
|
Hints []Hint
|
||||||
|
|
@ -28,8 +28,8 @@ func (h Hints) Swap(i, j int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Hints) Less(i, j int) bool {
|
func (h Hints) Less(i, j int) bool {
|
||||||
n, err1 := strconv.Atoi(h[i].mnemonic)
|
n, err1 := strconv.Atoi(h[i].Mnemonic)
|
||||||
m, err2 := strconv.Atoi(h[j].mnemonic)
|
m, err2 := strconv.Atoi(h[j].Mnemonic)
|
||||||
if err1 == nil && err2 == nil {
|
if err1 == nil && err2 == nil {
|
||||||
return n < m
|
return n < m
|
||||||
}
|
}
|
||||||
|
|
@ -39,5 +39,5 @@ func (h Hints) Less(i, j int) bool {
|
||||||
if err1 != nil && err2 == nil {
|
if err1 != nil && err2 == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return strings.Compare(h[i].description, h[j].description) < 0
|
return strings.Compare(h[i].Description, h[j].Description) < 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/config"
|
||||||
|
"github.com/derailed/tview"
|
||||||
|
"github.com/gdamore/tcell"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IndicatorView represents a status indicator.
|
||||||
|
type IndicatorView struct {
|
||||||
|
*tview.TextView
|
||||||
|
app *App
|
||||||
|
styles *config.Styles
|
||||||
|
permanent string
|
||||||
|
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIndicatorView returns a new logo.
|
||||||
|
func NewIndicatorView(app *App, styles *config.Styles) *IndicatorView {
|
||||||
|
v := IndicatorView{
|
||||||
|
TextView: tview.NewTextView(),
|
||||||
|
app: app,
|
||||||
|
styles: styles,
|
||||||
|
}
|
||||||
|
v.SetTextAlign(tview.AlignCenter)
|
||||||
|
v.SetTextColor(tcell.ColorWhite)
|
||||||
|
v.SetBackgroundColor(styles.BgColor())
|
||||||
|
v.SetDynamicColors(true)
|
||||||
|
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPermanent sets permanent title to be reset to after updates
|
||||||
|
func (v *IndicatorView) SetPermanent(info string) {
|
||||||
|
v.permanent = info
|
||||||
|
v.SetText(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset clears out the logo view and resets colors.
|
||||||
|
func (v *IndicatorView) Reset() {
|
||||||
|
v.Clear()
|
||||||
|
v.SetPermanent(v.permanent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err displays a log error state.
|
||||||
|
func (v *IndicatorView) Err(msg string) {
|
||||||
|
v.update(msg, "orangered")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn displays a log warning state.
|
||||||
|
func (v *IndicatorView) Warn(msg string) {
|
||||||
|
v.update(msg, "mediumvioletred")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info displays a log info state.
|
||||||
|
func (v *IndicatorView) Info(msg string) {
|
||||||
|
v.update(msg, "lawngreen")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *IndicatorView) update(msg, c string) {
|
||||||
|
v.setText(fmt.Sprintf("[%s::b] <%s> ", c, msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *IndicatorView) setText(msg string) {
|
||||||
|
if v.cancel != nil {
|
||||||
|
v.cancel()
|
||||||
|
}
|
||||||
|
v.SetText(msg)
|
||||||
|
|
||||||
|
var ctx context.Context
|
||||||
|
ctx, v.cancel = context.WithCancel(context.Background())
|
||||||
|
go func(ctx context.Context) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
v.app.QueueUpdateDraw(func() {
|
||||||
|
v.Reset()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}(ctx)
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package ui
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -68,12 +69,12 @@ func (v *MenuView) buildMenuTable(hh Hints) [][]string {
|
||||||
firstCmd := 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 && firstCmd {
|
if !isDigit && firstCmd {
|
||||||
row, col, firstCmd = 0, col+1, 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)
|
||||||
}
|
}
|
||||||
table[row][col] = h
|
table[row][col] = h
|
||||||
row++
|
row++
|
||||||
|
|
@ -89,7 +90,7 @@ func (v *MenuView) buildMenuTable(hh Hints) [][]string {
|
||||||
}
|
}
|
||||||
for row := range strTable {
|
for row := range strTable {
|
||||||
for col := range strTable[row] {
|
for col := range strTable[row] {
|
||||||
strTable[row][col] = v.formatMenu(table[row][col], maxKeys[col])
|
strTable[row][col] = keyConv(v.formatMenu(table[row][col], maxKeys[col]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,6 +100,18 @@ func (v *MenuView) buildMenuTable(hh Hints) [][]string {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
|
func keyConv(s string) string {
|
||||||
|
if !strings.Contains(s, "alt") {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS != "darwin" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Replace(s, "alt", "opt", 1)
|
||||||
|
}
|
||||||
|
|
||||||
func toMnemonic(s string) string {
|
func toMnemonic(s string) string {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
return s
|
return s
|
||||||
|
|
@ -108,9 +121,9 @@ func toMnemonic(s string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *MenuView) formatMenu(h Hint, size int) string {
|
func (v *MenuView) formatMenu(h Hint, size int) string {
|
||||||
i, err := strconv.Atoi(h.mnemonic)
|
i, err := strconv.Atoi(h.Mnemonic)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return formatNSMenu(i, h.description, v.styles.Frame())
|
return formatNSMenu(i, h.Description, v.styles.Frame())
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatPlainMenu(h, size, v.styles.Frame())
|
return formatPlainMenu(h, size, v.styles.Frame())
|
||||||
|
|
@ -128,7 +141,7 @@ func formatPlainMenu(h Hint, size int, styles config.Frame) string {
|
||||||
fmat := strings.Replace(menuFmt, "[key", "["+styles.Menu.KeyColor, 1)
|
fmat := strings.Replace(menuFmt, "[key", "["+styles.Menu.KeyColor, 1)
|
||||||
fmat = strings.Replace(fmat, "[fg", "["+styles.Menu.FgColor, 1)
|
fmat = strings.Replace(fmat, "[fg", "["+styles.Menu.FgColor, 1)
|
||||||
fmat = strings.Replace(fmat, ":bg:", ":"+styles.Title.BgColor+":", -1)
|
fmat = strings.Replace(fmat, ":bg:", ":"+styles.Title.BgColor+":", -1)
|
||||||
return fmt.Sprintf(fmat, toMnemonic(h.mnemonic), h.description)
|
return fmt.Sprintf(fmat, toMnemonic(h.Mnemonic), h.Description)
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -381,6 +383,37 @@ func (v *Table) filtered() resource.TableData {
|
||||||
}
|
}
|
||||||
|
|
||||||
q := v.cmdBuff.String()
|
q := v.cmdBuff.String()
|
||||||
|
if isFuzzySelector(q) {
|
||||||
|
return v.fuzzFilter(q[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.rxFilter(q)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Table) rxFilter(q string) resource.TableData {
|
||||||
|
rx, err := regexp.Compile(`(?i)` + v.cmdBuff.String())
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(errors.New("Invalid filter expression")).Msg("Regexp")
|
||||||
|
v.cmdBuff.Clear()
|
||||||
|
return v.data
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := resource.TableData{
|
||||||
|
Header: v.data.Header,
|
||||||
|
Rows: resource.RowEvents{},
|
||||||
|
Namespace: v.data.Namespace,
|
||||||
|
}
|
||||||
|
for k, row := range v.data.Rows {
|
||||||
|
f := strings.Join(row.Fields, " ")
|
||||||
|
if rx.MatchString(f) {
|
||||||
|
filtered.Rows[k] = row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Table) fuzzFilter(q string) resource.TableData {
|
||||||
var ss, kk []string
|
var ss, kk []string
|
||||||
for k, row := range v.data.Rows {
|
for k, row := range v.data.Rows {
|
||||||
ss = append(ss, row.Fields[v.NameColIndex()])
|
ss = append(ss, row.Fields[v.NameColIndex()])
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ var (
|
||||||
cpuRX = regexp.MustCompile(`\A.{0,1}CPU`)
|
cpuRX = regexp.MustCompile(`\A.{0,1}CPU`)
|
||||||
memRX = regexp.MustCompile(`\A.{0,1}MEM`)
|
memRX = regexp.MustCompile(`\A.{0,1}MEM`)
|
||||||
labelCmd = regexp.MustCompile(`\A\-l`)
|
labelCmd = regexp.MustCompile(`\A\-l`)
|
||||||
|
fuzzyCmd = regexp.MustCompile(`\A\-f`)
|
||||||
)
|
)
|
||||||
|
|
||||||
type cleanseFn func(string) string
|
type cleanseFn func(string) string
|
||||||
|
|
@ -47,6 +48,13 @@ func isLabelSelector(s string) bool {
|
||||||
return labelCmd.MatchString(s)
|
return labelCmd.MatchString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isFuzzySelector(s string) bool {
|
||||||
|
if s == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return fuzzyCmd.MatchString(s)
|
||||||
|
}
|
||||||
|
|
||||||
func trimLabelSelector(s string) string {
|
func trimLabelSelector(s string) string {
|
||||||
return strings.TrimSpace(s[2:])
|
return strings.TrimSpace(s[2:])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ package views
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
|
"github.com/derailed/k9s/internal/resource"
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
"github.com/derailed/k9s/internal/watch"
|
"github.com/derailed/k9s/internal/watch"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
|
|
@ -17,7 +19,7 @@ import (
|
||||||
const (
|
const (
|
||||||
splashTime = 1
|
splashTime = 1
|
||||||
devMode = "dev"
|
devMode = "dev"
|
||||||
clusterRefresh = time.Duration(15 * time.Second)
|
clusterRefresh = time.Duration(5 * time.Second)
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
@ -50,6 +52,7 @@ type (
|
||||||
informer *watch.Informer
|
informer *watch.Informer
|
||||||
stopCh chan struct{}
|
stopCh chan struct{}
|
||||||
forwarders map[string]forwarder
|
forwarders map[string]forwarder
|
||||||
|
version string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -63,6 +66,7 @@ func NewApp(cfg *config.Config) *appView {
|
||||||
v.InitBench(cfg.K9s.CurrentCluster)
|
v.InitBench(cfg.K9s.CurrentCluster)
|
||||||
v.command = newCommand(&v)
|
v.command = newCommand(&v)
|
||||||
|
|
||||||
|
v.Views()["indicator"] = ui.NewIndicatorView(v.App, v.Styles)
|
||||||
v.Views()["flash"] = ui.NewFlashView(v.Application, "Initializing...")
|
v.Views()["flash"] = ui.NewFlashView(v.Application, "Initializing...")
|
||||||
v.Views()["clusterInfo"] = newClusterInfoView(&v, k8s.NewMetricsServer(cfg.GetConnection()))
|
v.Views()["clusterInfo"] = newClusterInfoView(&v, k8s.NewMetricsServer(cfg.GetConnection()))
|
||||||
|
|
||||||
|
|
@ -70,6 +74,7 @@ func NewApp(cfg *config.Config) *appView {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *appView) Init(version string, rate int) {
|
func (a *appView) Init(version string, rate int) {
|
||||||
|
a.version = version
|
||||||
a.App.Init()
|
a.App.Init()
|
||||||
|
|
||||||
a.AddActions(ui.KeyActions{
|
a.AddActions(ui.KeyActions{
|
||||||
|
|
@ -85,6 +90,9 @@ func (a *appView) Init(version string, rate int) {
|
||||||
}
|
}
|
||||||
a.startInformer(ns)
|
a.startInformer(ns)
|
||||||
a.clusterInfo().init(version)
|
a.clusterInfo().init(version)
|
||||||
|
if a.Config.K9s.GetHeadless() {
|
||||||
|
a.refreshIndicator()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
header := tview.NewFlex()
|
header := tview.NewFlex()
|
||||||
|
|
@ -96,14 +104,17 @@ func (a *appView) Init(version string, rate int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
main := tview.NewFlex()
|
main := tview.NewFlex()
|
||||||
{
|
main.SetDirection(tview.FlexRow)
|
||||||
main.SetDirection(tview.FlexRow)
|
|
||||||
|
if !a.Config.K9s.GetHeadless() {
|
||||||
main.AddItem(header, 7, 1, false)
|
main.AddItem(header, 7, 1, false)
|
||||||
main.AddItem(a.Cmd(), 3, 1, false)
|
} else {
|
||||||
main.AddItem(a.Frame(), 0, 10, true)
|
main.AddItem(a.indicator(), 1, 1, false)
|
||||||
main.AddItem(a.Crumbs(), 2, 1, false)
|
|
||||||
main.AddItem(a.Flash(), 1, 1, false)
|
|
||||||
}
|
}
|
||||||
|
main.AddItem(a.Cmd(), 3, 1, false)
|
||||||
|
main.AddItem(a.Frame(), 0, 10, true)
|
||||||
|
main.AddItem(a.Crumbs(), 2, 1, false)
|
||||||
|
main.AddItem(a.Flash(), 1, 1, false)
|
||||||
|
|
||||||
a.Main().AddPage("main", main, true, false)
|
a.Main().AddPage("main", main, true, false)
|
||||||
a.Main().AddPage("splash", ui.NewSplash(a.Styles, version), true, true)
|
a.Main().AddPage("splash", ui.NewSplash(a.Styles, version), true, true)
|
||||||
|
|
@ -117,12 +128,45 @@ func (a *appView) clusterUpdater(ctx context.Context) {
|
||||||
return
|
return
|
||||||
case <-time.After(clusterRefresh):
|
case <-time.After(clusterRefresh):
|
||||||
a.QueueUpdateDraw(func() {
|
a.QueueUpdateDraw(func() {
|
||||||
|
if a.Config.K9s.GetHeadless() {
|
||||||
|
a.refreshIndicator()
|
||||||
|
}
|
||||||
a.clusterInfo().refresh()
|
a.clusterInfo().refresh()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *appView) refreshIndicator() {
|
||||||
|
mx := k8s.NewMetricsServer(a.Conn())
|
||||||
|
cluster := resource.NewCluster(a.Conn(), &log.Logger, mx)
|
||||||
|
var cmx k8s.ClusterMetrics
|
||||||
|
nos, nmx, err := fetchResources(a)
|
||||||
|
cpu, mem := "0", "0"
|
||||||
|
if err == nil {
|
||||||
|
cluster.Metrics(nos, nmx, &cmx)
|
||||||
|
cpu = resource.AsPerc(cmx.PercCPU)
|
||||||
|
if cpu == "0" {
|
||||||
|
cpu = resource.NAValue
|
||||||
|
}
|
||||||
|
mem = resource.AsPerc(cmx.PercMEM)
|
||||||
|
if mem == "0" {
|
||||||
|
mem = resource.NAValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info := fmt.Sprintf(
|
||||||
|
"[orange::b]K9s [aqua::]%s [white::]%s:%s:%s [lawngreen::]%s%%[white::]::[darkturquoise::]%s%%",
|
||||||
|
a.version,
|
||||||
|
cluster.ClusterName(),
|
||||||
|
cluster.UserName(),
|
||||||
|
cluster.Version(),
|
||||||
|
cpu,
|
||||||
|
mem,
|
||||||
|
)
|
||||||
|
a.indicator().SetPermanent(info)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *appView) startInformer(ns string) {
|
func (a *appView) startInformer(ns string) {
|
||||||
if a.stopCh != nil {
|
if a.stopCh != nil {
|
||||||
close(a.stopCh)
|
close(a.stopCh)
|
||||||
|
|
@ -135,6 +179,10 @@ func (a *appView) startInformer(ns string) {
|
||||||
log.Panic().Err(err).Msgf("%v", err)
|
log.Panic().Err(err).Msgf("%v", err)
|
||||||
}
|
}
|
||||||
a.informer.Run(a.stopCh)
|
a.informer.Run(a.stopCh)
|
||||||
|
|
||||||
|
if a.Config.K9s.GetHeadless() {
|
||||||
|
a.refreshIndicator()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BailOut exists the application.
|
// BailOut exists the application.
|
||||||
|
|
@ -188,6 +236,15 @@ func (a *appView) Run() {
|
||||||
|
|
||||||
func (a *appView) status(l ui.FlashLevel, msg string) {
|
func (a *appView) status(l ui.FlashLevel, msg string) {
|
||||||
a.Flash().Info(msg)
|
a.Flash().Info(msg)
|
||||||
|
if a.Config.K9s.GetHeadless() {
|
||||||
|
a.setIndicator(l, msg)
|
||||||
|
} else {
|
||||||
|
a.setLogo(l, msg)
|
||||||
|
}
|
||||||
|
a.Draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *appView) setLogo(l ui.FlashLevel, msg string) {
|
||||||
switch l {
|
switch l {
|
||||||
case ui.FlashErr:
|
case ui.FlashErr:
|
||||||
a.Logo().Err(msg)
|
a.Logo().Err(msg)
|
||||||
|
|
@ -201,6 +258,20 @@ func (a *appView) status(l ui.FlashLevel, msg string) {
|
||||||
a.Draw()
|
a.Draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *appView) setIndicator(l ui.FlashLevel, msg string) {
|
||||||
|
switch l {
|
||||||
|
case ui.FlashErr:
|
||||||
|
a.indicator().Err(msg)
|
||||||
|
case ui.FlashWarn:
|
||||||
|
a.indicator().Warn(msg)
|
||||||
|
case ui.FlashInfo:
|
||||||
|
a.indicator().Info(msg)
|
||||||
|
default:
|
||||||
|
a.indicator().Reset()
|
||||||
|
}
|
||||||
|
a.Draw()
|
||||||
|
}
|
||||||
|
|
||||||
func (a *appView) prevCmd(evt *tcell.EventKey) *tcell.EventKey {
|
func (a *appView) prevCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if top, ok := a.command.previousCmd(); ok {
|
if top, ok := a.command.previousCmd(); ok {
|
||||||
log.Debug().Msgf("Previous command %s", top)
|
log.Debug().Msgf("Previous command %s", top)
|
||||||
|
|
@ -225,7 +296,12 @@ func (a *appView) helpCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if a.InCmdMode() {
|
if a.InCmdMode() {
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
a.inject(newHelpView(a, a.ActiveView()))
|
if _, ok := a.Frame().GetPrimitive("main").(*helpView); ok {
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
|
||||||
|
h := newHelpView(a, a.ActiveView(), a.GetHints())
|
||||||
|
a.inject(h)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,6 +309,10 @@ func (a *appView) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if a.InCmdMode() {
|
if a.InCmdMode() {
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
if _, ok := a.Frame().GetPrimitive("main").(*aliasView); ok {
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
|
||||||
a.inject(newAliasView(a, a.ActiveView()))
|
a.inject(newAliasView(a, a.ActiveView()))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -267,3 +347,7 @@ func (a *appView) inject(i ui.Igniter) {
|
||||||
func (a *appView) clusterInfo() *clusterInfoView {
|
func (a *appView) clusterInfo() *clusterInfoView {
|
||||||
return a.Views()["clusterInfo"].(*clusterInfoView)
|
return a.Views()["clusterInfo"].(*clusterInfoView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *appView) indicator() *ui.IndicatorView {
|
||||||
|
return a.Views()["indicator"].(*ui.IndicatorView)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -125,13 +125,13 @@ func (v *clusterInfoView) refresh() {
|
||||||
v.refreshMetrics(cluster, row)
|
v.refreshMetrics(cluster, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *clusterInfoView) fetchResources() (k8s.Collection, k8s.Collection, error) {
|
func fetchResources(app *appView) (k8s.Collection, k8s.Collection, error) {
|
||||||
nos, err := v.app.informer.List(watch.NodeIndex, "", metav1.ListOptions{})
|
nos, err := app.informer.List(watch.NodeIndex, "", metav1.ListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
nmx, err := v.app.informer.List(watch.NodeMXIndex, "", metav1.ListOptions{})
|
nmx, err := app.informer.List(watch.NodeMXIndex, "", metav1.ListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -140,7 +140,7 @@ func (v *clusterInfoView) fetchResources() (k8s.Collection, k8s.Collection, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *clusterInfoView) refreshMetrics(cluster *resource.Cluster, row int) {
|
func (v *clusterInfoView) refreshMetrics(cluster *resource.Cluster, row int) {
|
||||||
nos, nmx, err := v.fetchResources()
|
nos, nmx, err := fetchResources(v.app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Msg("NodeMetrics")
|
log.Warn().Err(err).Msg("NodeMetrics")
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,10 @@ func (c *command) isStdCmd(cmd string) bool {
|
||||||
c.app.BailOut()
|
c.app.BailOut()
|
||||||
return true
|
return true
|
||||||
case cmd == "?", cmd == "help":
|
case cmd == "?", cmd == "help":
|
||||||
c.app.inject(newHelpView(c.app, c.app.ActiveView()))
|
c.app.helpCmd(nil)
|
||||||
return true
|
return true
|
||||||
case cmd == "alias":
|
case cmd == "alias":
|
||||||
c.app.inject(newAliasView(c.app, c.app.ActiveView()))
|
c.app.aliasCmd(nil)
|
||||||
return true
|
return true
|
||||||
case policyMatcher.MatchString(cmd):
|
case policyMatcher.MatchString(cmd):
|
||||||
tokens := policyMatcher.FindAllStringSubmatch(cmd, -1)
|
tokens := policyMatcher.FindAllStringSubmatch(cmd, -1)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ package views
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
|
|
@ -21,7 +24,7 @@ type (
|
||||||
}
|
}
|
||||||
|
|
||||||
helpView struct {
|
helpView struct {
|
||||||
*tview.TextView
|
*tview.Table
|
||||||
|
|
||||||
app *appView
|
app *appView
|
||||||
current ui.Igniter
|
current ui.Igniter
|
||||||
|
|
@ -29,19 +32,18 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func newHelpView(app *appView, current ui.Igniter) *helpView {
|
func newHelpView(app *appView, current ui.Igniter, hh ui.Hints) *helpView {
|
||||||
v := helpView{
|
v := helpView{
|
||||||
TextView: tview.NewTextView(),
|
Table: tview.NewTable(),
|
||||||
app: app,
|
app: app,
|
||||||
actions: make(ui.KeyActions),
|
actions: make(ui.KeyActions),
|
||||||
}
|
}
|
||||||
v.SetTextColor(tcell.ColorAqua)
|
|
||||||
v.SetBorder(true)
|
v.SetBorder(true)
|
||||||
v.SetBorderPadding(0, 0, 1, 1)
|
v.SetBorderPadding(0, 0, 1, 1)
|
||||||
v.SetDynamicColors(true)
|
|
||||||
v.SetInputCapture(v.keyboard)
|
v.SetInputCapture(v.keyboard)
|
||||||
v.current = current
|
v.current = current
|
||||||
v.bindKeys()
|
v.bindKeys()
|
||||||
|
v.build(hh)
|
||||||
|
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
@ -73,26 +75,18 @@ func (v *helpView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
|
||||||
func (v *helpView) Init(_ context.Context, _ string) {
|
func (v *helpView) Init(_ context.Context, _ string) {
|
||||||
v.resetTitle()
|
v.resetTitle()
|
||||||
|
|
||||||
v.showGeneral()
|
|
||||||
v.showNav()
|
|
||||||
v.showHelp()
|
|
||||||
v.app.SetHints(v.Hints())
|
v.app.SetHints(v.Hints())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *helpView) showHelp() {
|
func (v *helpView) showHelp() ui.Hints {
|
||||||
views := []helpItem{
|
return ui.Hints{
|
||||||
{"?", "Help"},
|
{"?", "Help"},
|
||||||
{"Ctrl-a", "Aliases view"},
|
{"Ctrl-a", "Aliases view"},
|
||||||
}
|
}
|
||||||
fmt.Fprintf(v, "️️\n😱 [aqua::b]%s\n", "Help")
|
|
||||||
for _, h := range views {
|
|
||||||
v.printHelp(h.key, h.description)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *helpView) showNav() {
|
func (v *helpView) showNav() ui.Hints {
|
||||||
navigation := []helpItem{
|
return ui.Hints{
|
||||||
{"g", "Goto Top"},
|
{"g", "Goto Top"},
|
||||||
{"G", "Goto Bottom"},
|
{"G", "Goto Bottom"},
|
||||||
{"Ctrl-b", "Page Down"},
|
{"Ctrl-b", "Page Down"},
|
||||||
|
|
@ -102,14 +96,10 @@ func (v *helpView) showNav() {
|
||||||
{"k", "Up"},
|
{"k", "Up"},
|
||||||
{"j", "Down"},
|
{"j", "Down"},
|
||||||
}
|
}
|
||||||
fmt.Fprintf(v, "\n🤖 [aqua::b]%s\n", "View Navigation")
|
|
||||||
for _, h := range navigation {
|
|
||||||
v.printHelp(h.key, h.description)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *helpView) showGeneral() {
|
func (v *helpView) showGeneral() ui.Hints {
|
||||||
general := []helpItem{
|
return ui.Hints{
|
||||||
{":<cmd>", "Command mode"},
|
{":<cmd>", "Command mode"},
|
||||||
{"/<term>", "Filter mode"},
|
{"/<term>", "Filter mode"},
|
||||||
{"esc", "Clear filter"},
|
{"esc", "Clear filter"},
|
||||||
|
|
@ -120,14 +110,6 @@ func (v *helpView) showGeneral() {
|
||||||
{"p", "Previous resource view"},
|
{"p", "Previous resource view"},
|
||||||
{":q", "Quit"},
|
{":q", "Quit"},
|
||||||
}
|
}
|
||||||
fmt.Fprintf(v, "🏠 [aqua::b]%s\n", "General")
|
|
||||||
for _, h := range general {
|
|
||||||
v.printHelp(h.key, h.description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *helpView) printHelp(key, desc string) {
|
|
||||||
fmt.Fprintf(v, "[dodgerblue::b]%9s [white::]%s\n", key, desc)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *helpView) Hints() ui.Hints {
|
func (v *helpView) Hints() ui.Hints {
|
||||||
|
|
@ -141,3 +123,55 @@ func (v *helpView) getTitle() string {
|
||||||
func (v *helpView) resetTitle() {
|
func (v *helpView) resetTitle() {
|
||||||
v.SetTitle(fmt.Sprintf(helpTitleFmt, helpTitle))
|
v.SetTitle(fmt.Sprintf(helpTitleFmt, helpTitle))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *helpView) build(hh ui.Hints) {
|
||||||
|
v.Clear()
|
||||||
|
sort.Sort(hh)
|
||||||
|
v.addSection(0, 0, "Resource", hh)
|
||||||
|
v.addSection(0, 4, "General", v.showGeneral())
|
||||||
|
v.addSection(0, 6, "Navigation", v.showNav())
|
||||||
|
v.addSection(0, 8, "Help", v.showHelp())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *helpView) addSection(r, c int, title string, hh ui.Hints) {
|
||||||
|
row := r
|
||||||
|
cell := tview.NewTableCell(title)
|
||||||
|
cell.SetTextColor(tcell.ColorWhite)
|
||||||
|
cell.SetAttributes(tcell.AttrBold)
|
||||||
|
v.SetCell(r, c, cell)
|
||||||
|
row++
|
||||||
|
|
||||||
|
for _, h := range hh {
|
||||||
|
col := c
|
||||||
|
cell := tview.NewTableCell(toMnemonic(h.Mnemonic))
|
||||||
|
cell.SetTextColor(tcell.ColorDodgerBlue)
|
||||||
|
cell.SetAttributes(tcell.AttrBold)
|
||||||
|
cell.SetAlign(tview.AlignRight)
|
||||||
|
v.SetCell(row, col, cell)
|
||||||
|
col++
|
||||||
|
cell = tview.NewTableCell(h.Description)
|
||||||
|
cell.SetTextColor(tcell.ColorWhite)
|
||||||
|
v.SetCell(row, col, cell)
|
||||||
|
row++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toMnemonic(s string) string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return "<" + keyConv(strings.ToLower(s)) + ">"
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyConv(s string) string {
|
||||||
|
if !strings.Contains(s, "alt") {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS != "darwin" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Replace(s, "alt", "opt", 1)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
|
"github.com/derailed/k9s/internal/ui"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
@ -40,10 +41,10 @@ func newNS(n string) v1.Namespace {
|
||||||
func TestNewHelpView(t *testing.T) {
|
func TestNewHelpView(t *testing.T) {
|
||||||
cfg := config.NewConfig(ks{})
|
cfg := config.NewConfig(ks{})
|
||||||
a := NewApp(cfg)
|
a := NewApp(cfg)
|
||||||
v := newHelpView(a, nil)
|
|
||||||
|
v := newHelpView(a, nil, ui.Hints{{"blee", "duh"}})
|
||||||
v.Init(nil, "")
|
v.Init(nil, "")
|
||||||
|
|
||||||
const e = "🏠 General\n :<cmd> Command mode\n /<term> Filter mode\n esc Clear filter\n tab Next term match\n backtab Previous term match\n Ctrl-r Refresh\n Shift-i Invert Sort\n p Previous resource view\n :q Quit\n\n🤖 View Navigation\n g Goto Top\n G Goto Bottom\n Ctrl-b Page Down\n Ctrl-f Page Up\n h Left\n l Right\n k Up\n j Down\n️️\n😱 Help\n ? Help\n Ctrl-a Aliases view\n"
|
assert.Equal(t, "<blee>", v.GetCell(1, 0).Text)
|
||||||
assert.Equal(t, e, v.GetText(true))
|
assert.Equal(t, "duh", v.GetCell(1, 1).Text)
|
||||||
assert.Equal(t, "Help", v.getTitle())
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue