test pass
parent
4601d95e0f
commit
a7e4d890f3
|
|
@ -21,13 +21,13 @@ type aliasView struct {
|
|||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func newAliasView(app *appView) *aliasView {
|
||||
func newAliasView(app *appView, current igniter) *aliasView {
|
||||
v := aliasView{tableView: newTableView(app, aliasTitle)}
|
||||
{
|
||||
v.SetBorderFocusColor(tcell.ColorFuchsia)
|
||||
v.SetSelectedStyle(tcell.ColorWhite, tcell.ColorFuchsia, tcell.AttrNone)
|
||||
v.colorerFn = aliasColorer
|
||||
v.current = app.content.GetPrimitive("main").(igniter)
|
||||
v.current = current
|
||||
v.currentNS = ""
|
||||
v.registerActions()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAliasView(t *testing.T) {
|
||||
v := newAliasView(NewApp(config.NewConfig(ks{})), nil)
|
||||
td := v.hydrate()
|
||||
v.init(nil, "")
|
||||
|
||||
assert.Equal(t, 3, len(td.Header))
|
||||
assert.Equal(t, 31, len(td.Rows))
|
||||
assert.Equal(t, "Aliases", v.getTitle())
|
||||
}
|
||||
|
|
@ -8,10 +8,8 @@ import (
|
|||
"github.com/derailed/k9s/internal/k8s"
|
||||
"github.com/derailed/k9s/internal/watch"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rs/zerolog/log"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/client-go/tools/portforward"
|
||||
)
|
||||
|
||||
|
|
@ -41,12 +39,6 @@ type (
|
|||
init(ctx context.Context, ns string)
|
||||
}
|
||||
|
||||
keyHandler interface {
|
||||
keyboard(evt *tcell.EventKey) *tcell.EventKey
|
||||
}
|
||||
|
||||
actionsFn func(keyActions)
|
||||
|
||||
resourceViewer interface {
|
||||
igniter
|
||||
|
||||
|
|
@ -57,101 +49,80 @@ type (
|
|||
}
|
||||
|
||||
appView struct {
|
||||
*tview.Application
|
||||
*shellView
|
||||
|
||||
config *config.Config
|
||||
styles *config.Styles
|
||||
bench *config.Bench
|
||||
version string
|
||||
flags *genericclioptions.ConfigFlags
|
||||
pages *tview.Pages
|
||||
content *tview.Pages
|
||||
flashView *flashView
|
||||
crumbsView *crumbsView
|
||||
logoView *logoView
|
||||
menuView *menuView
|
||||
clusterInfoView *clusterInfoView
|
||||
cmdView *cmdView
|
||||
command *command
|
||||
focusGroup []tview.Primitive
|
||||
focusCurrent int
|
||||
focusChanged focusHandler
|
||||
cancel context.CancelFunc
|
||||
cancelSkin context.CancelFunc
|
||||
cmdBuff *cmdBuff
|
||||
actions keyActions
|
||||
stopCh chan struct{}
|
||||
informer *watch.Informer
|
||||
forwarders []forwarder
|
||||
hasSkins bool
|
||||
cmdBuff *cmdBuff
|
||||
command *command
|
||||
cancel context.CancelFunc
|
||||
informer *watch.Informer
|
||||
stopCh chan struct{}
|
||||
forwarders []forwarder
|
||||
}
|
||||
)
|
||||
|
||||
// NewApp returns a K9s app instance.
|
||||
func NewApp(cfg *config.Config) *appView {
|
||||
v := appView{
|
||||
Application: tview.NewApplication(),
|
||||
config: cfg,
|
||||
pages: tview.NewPages(),
|
||||
actions: make(keyActions),
|
||||
content: tview.NewPages(),
|
||||
cmdBuff: newCmdBuff(':'),
|
||||
}
|
||||
{
|
||||
v.initBench(cfg.K9s.CurrentCluster)
|
||||
v.refreshStyles()
|
||||
v.menuView = newMenuView(&v)
|
||||
v.logoView = newLogoView(&v)
|
||||
v.cmdView = newCmdView(&v, '🐶')
|
||||
v.command = newCommand(&v)
|
||||
v.flashView = newFlashView(&v, "Initializing...")
|
||||
v.crumbsView = newCrumbsView(&v)
|
||||
v.clusterInfoView = newClusterInfoView(&v, k8s.NewMetricsServer(cfg.GetConnection()))
|
||||
v.focusChanged = v.changedFocus
|
||||
v.SetInputCapture(v.keyboard)
|
||||
shellView: newShellView(),
|
||||
cmdBuff: newCmdBuff(':'),
|
||||
}
|
||||
|
||||
v.actions[KeyColon] = newKeyAction("Cmd", v.activateCmd, false)
|
||||
v.actions[tcell.KeyCtrlR] = newKeyAction("Redraw", v.redrawCmd, false)
|
||||
v.actions[tcell.KeyCtrlC] = newKeyAction("Quit", v.quitCmd, false)
|
||||
v.actions[KeyHelp] = newKeyAction("Help", v.helpCmd, false)
|
||||
v.actions[tcell.KeyCtrlA] = newKeyAction("Aliases", v.aliasCmd, true)
|
||||
v.actions[tcell.KeyEscape] = newKeyAction("Exit Cmd", v.deactivateCmd, false)
|
||||
v.actions[tcell.KeyEnter] = newKeyAction("Goto", v.gotoCmd, false)
|
||||
v.actions[tcell.KeyBackspace2] = newKeyAction("Erase", v.eraseCmd, false)
|
||||
v.actions[tcell.KeyBackspace] = newKeyAction("Erase", v.eraseCmd, false)
|
||||
v.actions[tcell.KeyDelete] = newKeyAction("Erase", v.eraseCmd, false)
|
||||
v.config = cfg
|
||||
v.initBench(cfg.K9s.CurrentCluster)
|
||||
v.refreshStyles()
|
||||
|
||||
v.views["menu"] = newMenuView(v.styles)
|
||||
v.views["logo"] = newLogoView(v.styles)
|
||||
v.views["cmd"] = newCmdView(v.styles, '🐶')
|
||||
v.command = newCommand(&v)
|
||||
v.views["flash"] = newFlashView(&v, "Initializing...")
|
||||
v.views["crumbs"] = newCrumbsView(v.styles)
|
||||
v.views["clusterInfo"] = newClusterInfoView(&v, k8s.NewMetricsServer(cfg.GetConnection()))
|
||||
|
||||
v.SetInputCapture(v.keyboard)
|
||||
v.registerActions()
|
||||
|
||||
return &v
|
||||
}
|
||||
|
||||
func (a *appView) Init(v string, rate int, flags *genericclioptions.ConfigFlags) {
|
||||
a.version = v
|
||||
a.flags = flags
|
||||
func (a *appView) registerActions() {
|
||||
a.actions[KeyColon] = newKeyAction("Cmd", a.activateCmd, false)
|
||||
a.actions[tcell.KeyCtrlR] = newKeyAction("Redraw", a.redrawCmd, false)
|
||||
a.actions[tcell.KeyCtrlC] = newKeyAction("Quit", a.quitCmd, false)
|
||||
a.actions[KeyHelp] = newKeyAction("Help", a.helpCmd, false)
|
||||
a.actions[tcell.KeyCtrlA] = newKeyAction("Aliases", a.aliasCmd, true)
|
||||
a.actions[tcell.KeyEscape] = newKeyAction("Escape", a.escapeCmd, false)
|
||||
a.actions[tcell.KeyEnter] = newKeyAction("Goto", a.gotoCmd, false)
|
||||
a.actions[tcell.KeyBackspace2] = newKeyAction("Erase", a.eraseCmd, false)
|
||||
a.actions[tcell.KeyBackspace] = newKeyAction("Erase", a.eraseCmd, false)
|
||||
a.actions[tcell.KeyDelete] = newKeyAction("Erase", a.eraseCmd, false)
|
||||
}
|
||||
|
||||
func (a *appView) Init(version string, rate int) {
|
||||
a.startInformer()
|
||||
a.clusterInfoView.init()
|
||||
a.cmdBuff.addListener(a.cmdView)
|
||||
a.clusterInfo().init(version)
|
||||
a.cmdBuff.addListener(a.cmd())
|
||||
|
||||
header := tview.NewFlex()
|
||||
{
|
||||
header.SetDirection(tview.FlexColumn)
|
||||
header.AddItem(a.clusterInfoView, 35, 1, false)
|
||||
header.AddItem(a.menuView, 0, 1, false)
|
||||
header.AddItem(a.logoView, 26, 1, false)
|
||||
header.AddItem(a.clusterInfo(), 35, 1, false)
|
||||
header.AddItem(a.views["menu"], 0, 1, false)
|
||||
header.AddItem(a.logo(), 26, 1, false)
|
||||
}
|
||||
|
||||
main := tview.NewFlex()
|
||||
{
|
||||
main.SetDirection(tview.FlexRow)
|
||||
main.AddItem(header, 7, 1, false)
|
||||
main.AddItem(a.cmdView, 3, 1, false)
|
||||
main.AddItem(a.cmd(), 3, 1, false)
|
||||
main.AddItem(a.content, 0, 10, true)
|
||||
main.AddItem(a.crumbsView, 2, 1, false)
|
||||
main.AddItem(a.flashView, 1, 1, false)
|
||||
main.AddItem(a.crumbs(), 2, 1, false)
|
||||
main.AddItem(a.flash(), 1, 1, false)
|
||||
}
|
||||
|
||||
a.pages.AddPage("main", main, true, false)
|
||||
a.pages.AddPage("splash", newSplash(a), true, true)
|
||||
a.pages.AddPage("splash", newSplash(a.styles, version), true, true)
|
||||
a.SetRoot(a.pages, true)
|
||||
}
|
||||
|
||||
|
|
@ -163,7 +134,7 @@ func (a *appView) clusterUpdater(ctx context.Context) {
|
|||
return
|
||||
case <-time.After(clusterRefresh):
|
||||
a.QueueUpdateDraw(func() {
|
||||
a.clusterInfoView.refresh()
|
||||
a.clusterInfo().refresh()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -175,9 +146,9 @@ func (a *appView) startInformer() {
|
|||
}
|
||||
|
||||
a.stopCh = make(chan struct{})
|
||||
ns := ""
|
||||
if a.flags.Namespace != nil {
|
||||
ns = *a.flags.Namespace
|
||||
ns, err := a.conn().Config().CurrentNamespaceName()
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("No namespace specified using all namespaces")
|
||||
}
|
||||
a.informer = watch.NewInformer(a.conn(), ns)
|
||||
a.informer.Run(a.stopCh)
|
||||
|
|
@ -193,11 +164,6 @@ func (a *appView) BailOut() {
|
|||
|
||||
if a.cancel != nil {
|
||||
a.cancel()
|
||||
a.cancel = nil
|
||||
}
|
||||
if a.cancelSkin != nil {
|
||||
a.cancelSkin()
|
||||
a.cancelSkin = nil
|
||||
}
|
||||
a.stopForwarders()
|
||||
a.Stop()
|
||||
|
|
@ -215,33 +181,6 @@ func (a *appView) conn() k8s.Connection {
|
|||
return a.config.GetConnection()
|
||||
}
|
||||
|
||||
func (a *appView) stylesUpdater(ctx context.Context) error {
|
||||
w, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case evt := <-w.Events:
|
||||
_ = evt
|
||||
a.QueueUpdateDraw(func() {
|
||||
a.refreshStyles()
|
||||
})
|
||||
case err := <-w.Errors:
|
||||
log.Info().Err(err).Msg("Skin watcher failed")
|
||||
return
|
||||
case <-ctx.Done():
|
||||
w.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return w.Add(config.K9sStylesFile)
|
||||
}
|
||||
|
||||
// Run starts the application loop
|
||||
func (a *appView) Run() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
|
@ -249,10 +188,9 @@ func (a *appView) Run() {
|
|||
go a.clusterUpdater(ctx)
|
||||
|
||||
// Only enable skin updater while in dev mode.
|
||||
if a.version == devMode && a.hasSkins {
|
||||
if a.hasSkins {
|
||||
var ctx context.Context
|
||||
ctx, a.cancelSkin = context.WithCancel(context.Background())
|
||||
if err := a.stylesUpdater(ctx); err != nil {
|
||||
if err := a.stylesUpdater(ctx, a); err != nil {
|
||||
log.Error().Err(err).Msg("Unable to track skin changes")
|
||||
}
|
||||
}
|
||||
|
|
@ -270,8 +208,16 @@ func (a *appView) Run() {
|
|||
}
|
||||
}
|
||||
|
||||
func (a *appView) crumbs() *crumbsView {
|
||||
return a.views["crumbs"].(*crumbsView)
|
||||
}
|
||||
|
||||
func (a *appView) logo() *logoView {
|
||||
return a.views["logo"].(*logoView)
|
||||
}
|
||||
|
||||
func (a *appView) statusReset() {
|
||||
a.logoView.reset()
|
||||
a.logo().reset()
|
||||
a.Draw()
|
||||
}
|
||||
|
||||
|
|
@ -280,13 +226,13 @@ func (a *appView) status(l flashLevel, msg string) {
|
|||
|
||||
switch l {
|
||||
case flashErr:
|
||||
a.logoView.err(msg)
|
||||
a.logo().err(msg)
|
||||
case flashWarn:
|
||||
a.logoView.warn(msg)
|
||||
a.logo().warn(msg)
|
||||
case flashInfo:
|
||||
a.logoView.info(msg)
|
||||
a.logo().info(msg)
|
||||
default:
|
||||
a.logoView.reset()
|
||||
a.logo().reset()
|
||||
}
|
||||
a.Draw()
|
||||
}
|
||||
|
|
@ -322,11 +268,6 @@ func (a *appView) redrawCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
|
||||
func (a *appView) focusCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
a.nextFocus()
|
||||
return evt
|
||||
}
|
||||
|
||||
func (a *appView) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if a.cmdBuff.isActive() {
|
||||
a.cmdBuff.del()
|
||||
|
|
@ -335,7 +276,7 @@ func (a *appView) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
|
||||
func (a *appView) deactivateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
func (a *appView) escapeCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if a.cmdBuff.isActive() {
|
||||
a.cmdBuff.reset()
|
||||
}
|
||||
|
|
@ -362,10 +303,10 @@ func (a *appView) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (a *appView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if a.cmdView.inCmdMode() {
|
||||
if a.inCmdMode() {
|
||||
return evt
|
||||
}
|
||||
a.flashView.info("Command mode activated.")
|
||||
a.flash().info("Command mode activated.")
|
||||
log.Debug().Msg("Entering command mode...")
|
||||
a.cmdBuff.setActive(true)
|
||||
a.cmdBuff.clear()
|
||||
|
|
@ -373,7 +314,7 @@ func (a *appView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (a *appView) quitCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if a.cmdMode() {
|
||||
if a.inCmdMode() {
|
||||
return evt
|
||||
}
|
||||
a.BailOut()
|
||||
|
|
@ -382,24 +323,27 @@ func (a *appView) quitCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (a *appView) helpCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if a.cmdView.inCmdMode() {
|
||||
if a.inCmdMode() {
|
||||
return evt
|
||||
}
|
||||
a.inject(newHelpView(a))
|
||||
a.inject(newHelpView(a, a.currentView()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *appView) currentView() igniter {
|
||||
return a.content.GetPrimitive("main").(igniter)
|
||||
}
|
||||
func (a *appView) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if a.cmdView.inCmdMode() {
|
||||
if a.inCmdMode() {
|
||||
return evt
|
||||
}
|
||||
|
||||
a.inject(newAliasView(a))
|
||||
a.inject(newAliasView(a, a.currentView()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *appView) fwdCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if a.cmdView.inCmdMode() {
|
||||
if a.inCmdMode() {
|
||||
return evt
|
||||
}
|
||||
|
||||
|
|
@ -438,76 +382,29 @@ func (a *appView) inject(i igniter) {
|
|||
i.init(ctx, a.config.ActiveNamespace())
|
||||
}
|
||||
a.content.AddPage("main", i, true, true)
|
||||
|
||||
a.focusGroup = append([]tview.Primitive{}, i)
|
||||
a.focusCurrent = 0
|
||||
a.fireFocusChanged(i)
|
||||
a.SetFocus(i)
|
||||
}
|
||||
|
||||
func (a *appView) cmdMode() bool {
|
||||
return a.cmdView.inCmdMode()
|
||||
}
|
||||
|
||||
func (a *appView) flash() *flashView {
|
||||
return a.flashView
|
||||
return a.views["flash"].(*flashView)
|
||||
}
|
||||
|
||||
func (a *appView) setHints(h hints) {
|
||||
a.menuView.populateMenu(h)
|
||||
a.views["menu"].(*menuView).populateMenu(h)
|
||||
}
|
||||
|
||||
func (a *appView) fireFocusChanged(p tview.Primitive) {
|
||||
if a.focusChanged != nil {
|
||||
a.focusChanged(p)
|
||||
}
|
||||
func (a *appView) clusterInfo() *clusterInfoView {
|
||||
return a.views["clusterInfo"].(*clusterInfoView)
|
||||
}
|
||||
|
||||
func (a *appView) changedFocus(p tview.Primitive) {
|
||||
switch p.(type) {
|
||||
case hinter:
|
||||
a.setHints(p.(hinter).hints())
|
||||
}
|
||||
func (a *appView) clusterInfoRefresh() {
|
||||
a.clusterInfo().refresh()
|
||||
}
|
||||
|
||||
func (a *appView) nextFocus() {
|
||||
for i := range a.focusGroup {
|
||||
if i == a.focusCurrent {
|
||||
a.focusCurrent = 0
|
||||
victim := a.focusGroup[a.focusCurrent]
|
||||
if i+1 < len(a.focusGroup) {
|
||||
a.focusCurrent++
|
||||
victim = a.focusGroup[a.focusCurrent]
|
||||
}
|
||||
a.fireFocusChanged(victim)
|
||||
a.SetFocus(victim)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
func (a *appView) cmd() *cmdView {
|
||||
return a.views["cmd"].(*cmdView)
|
||||
}
|
||||
|
||||
func (a *appView) initBench(cluster string) {
|
||||
var err error
|
||||
if a.bench, err = config.NewBench(benchConfig(cluster)); err != nil {
|
||||
log.Warn().Err(err).Msg("No benchmark config file found, using defaults.")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *appView) refreshStyles() {
|
||||
var err error
|
||||
if a.styles, err = config.NewStyles(config.K9sStylesFile); err != nil {
|
||||
log.Warn().Err(err).Msg("No skin file found. Loading defaults.")
|
||||
}
|
||||
if err == nil {
|
||||
a.hasSkins = true
|
||||
}
|
||||
a.styles.Update()
|
||||
|
||||
stdColor = config.AsColor(a.styles.Style.Status.NewColor)
|
||||
addColor = config.AsColor(a.styles.Style.Status.AddColor)
|
||||
modColor = config.AsColor(a.styles.Style.Status.ModifyColor)
|
||||
errColor = config.AsColor(a.styles.Style.Status.ErrorColor)
|
||||
highlightColor = config.AsColor(a.styles.Style.Status.HighlightColor)
|
||||
completedColor = config.AsColor(a.styles.Style.Status.CompletedColor)
|
||||
func (a *appView) inCmdMode() bool {
|
||||
return a.cmd().inCmdMode()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package views
|
||||
|
||||
// import (
|
||||
// "testing"
|
||||
|
||||
// "github.com/derailed/k9s/internal/config"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
|
||||
// func TestNewApp(t *testing.T) {
|
||||
// mk := NewMockKubeSettings()
|
||||
// cfg := config.NewConfig(mk)
|
||||
// a := NewApp(cfg)
|
||||
// a.Init("blee", 10)
|
||||
|
||||
// assert.Equal(t, 10, len(a.actions))
|
||||
// assert.Equal(t, false, a.hasSkins)
|
||||
// }
|
||||
|
|
@ -39,7 +39,7 @@ func newClusterInfoView(app *appView, mx resource.MetricsServer) *clusterInfoVie
|
|||
}
|
||||
}
|
||||
|
||||
func (v *clusterInfoView) init() {
|
||||
func (v *clusterInfoView) init(version string) {
|
||||
cluster := resource.NewCluster(v.app.conn(), &log.Logger, v.mxs)
|
||||
|
||||
var row int
|
||||
|
|
@ -56,7 +56,7 @@ func (v *clusterInfoView) init() {
|
|||
row++
|
||||
|
||||
v.SetCell(row, 0, v.sectionCell("K9s Rev"))
|
||||
v.SetCell(row, 1, v.infoCell(v.app.version))
|
||||
v.SetCell(row, 1, v.infoCell(version))
|
||||
row++
|
||||
|
||||
rev := cluster.Version()
|
||||
|
|
|
|||
|
|
@ -15,20 +15,20 @@ type cmdView struct {
|
|||
activated bool
|
||||
icon rune
|
||||
text string
|
||||
app *appView
|
||||
styles *config.Styles
|
||||
}
|
||||
|
||||
func newCmdView(app *appView, ic rune) *cmdView {
|
||||
v := cmdView{app: app, icon: ic, TextView: tview.NewTextView()}
|
||||
func newCmdView(styles *config.Styles, ic rune) *cmdView {
|
||||
v := cmdView{styles: styles, icon: ic, TextView: tview.NewTextView()}
|
||||
{
|
||||
v.SetWordWrap(true)
|
||||
v.SetWrap(true)
|
||||
v.SetDynamicColors(true)
|
||||
v.SetBorder(true)
|
||||
v.SetBorderPadding(0, 0, 1, 1)
|
||||
v.SetBackgroundColor(app.styles.BgColor())
|
||||
v.SetBorderColor(config.AsColor(app.styles.Style.Border.FocusColor))
|
||||
v.SetTextColor(app.styles.FgColor())
|
||||
v.SetBackgroundColor(styles.BgColor())
|
||||
v.SetBorderColor(config.AsColor(styles.Style.Border.FocusColor))
|
||||
v.SetTextColor(styles.FgColor())
|
||||
}
|
||||
return &v
|
||||
}
|
||||
|
|
@ -66,11 +66,11 @@ func (v *cmdView) active(f bool) {
|
|||
v.activated = f
|
||||
if f {
|
||||
v.SetBorder(true)
|
||||
v.SetTextColor(v.app.styles.FgColor())
|
||||
v.SetTextColor(v.styles.FgColor())
|
||||
v.activate()
|
||||
} else {
|
||||
v.SetBorder(false)
|
||||
v.SetBackgroundColor(v.app.styles.BgColor())
|
||||
v.SetBackgroundColor(v.styles.BgColor())
|
||||
v.Clear()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewCmdUpdate(t *testing.T) {
|
||||
defaults, _ := config.NewStyles("")
|
||||
v := newCmdView(defaults, 'T')
|
||||
v.update("blee")
|
||||
|
||||
assert.Equal(t, "T> blee\n", v.GetText(false))
|
||||
}
|
||||
|
||||
func TestNewCmdActivate(t *testing.T) {
|
||||
defaults, _ := config.NewStyles("")
|
||||
v := newCmdView(defaults, 'T')
|
||||
v.update("blee")
|
||||
v.append('!')
|
||||
|
||||
assert.Equal(t, "T> blee!\n", v.GetText(false))
|
||||
assert.False(t, v.inCmdMode())
|
||||
v.active(true)
|
||||
assert.True(t, v.inCmdMode())
|
||||
}
|
||||
|
|
@ -28,12 +28,12 @@ func (c *command) lastCmd() bool {
|
|||
|
||||
func (c *command) pushCmd(cmd string) {
|
||||
c.history.push(cmd)
|
||||
c.app.crumbsView.update(c.history.stack)
|
||||
c.app.crumbs().update(c.history.stack)
|
||||
}
|
||||
|
||||
func (c *command) previousCmd() (string, bool) {
|
||||
c.history.pop()
|
||||
c.app.crumbsView.update(c.history.stack)
|
||||
c.app.crumbs().update(c.history.stack)
|
||||
|
||||
return c.history.top()
|
||||
}
|
||||
|
|
@ -56,10 +56,10 @@ func (c *command) run(cmd string) bool {
|
|||
c.app.BailOut()
|
||||
return true
|
||||
case cmd == "?", cmd == "help":
|
||||
c.app.inject(newHelpView(c.app))
|
||||
c.app.inject(newHelpView(c.app, c.app.currentView()))
|
||||
return true
|
||||
case cmd == "alias":
|
||||
c.app.inject(newAliasView(c.app))
|
||||
c.app.inject(newAliasView(c.app, c.app.currentView()))
|
||||
return true
|
||||
case policyMatcher.MatchString(cmd):
|
||||
tokens := policyMatcher.FindAllStringSubmatch(cmd, -1)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type synchronizer interface {
|
||||
QueueUpdateDraw(func()) *tview.Application
|
||||
QueueUpdate(func()) *tview.Application
|
||||
}
|
||||
|
||||
type configurator struct {
|
||||
hasSkins bool
|
||||
config *config.Config
|
||||
styles *config.Styles
|
||||
bench *config.Bench
|
||||
}
|
||||
|
||||
func (c *configurator) stylesUpdater(ctx context.Context, s synchronizer) error {
|
||||
w, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case evt := <-w.Events:
|
||||
_ = evt
|
||||
s.QueueUpdateDraw(func() {
|
||||
c.refreshStyles()
|
||||
})
|
||||
case err := <-w.Errors:
|
||||
log.Info().Err(err).Msg("Skin watcher failed")
|
||||
return
|
||||
case <-ctx.Done():
|
||||
w.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return w.Add(config.K9sStylesFile)
|
||||
}
|
||||
|
||||
func (c *configurator) initBench(cluster string) {
|
||||
var err error
|
||||
if c.bench, err = config.NewBench(benchConfig(cluster)); err != nil {
|
||||
log.Warn().Err(err).Msg("No benchmark config file found, using defaults.")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *configurator) refreshStyles() {
|
||||
var err error
|
||||
if c.styles, err = config.NewStyles(config.K9sStylesFile); err != nil {
|
||||
log.Warn().Err(err).Msg("No skin file found. Loading defaults.")
|
||||
}
|
||||
if err == nil {
|
||||
c.hasSkins = true
|
||||
}
|
||||
c.styles.Update()
|
||||
|
||||
stdColor = config.AsColor(c.styles.Style.Status.NewColor)
|
||||
addColor = config.AsColor(c.styles.Style.Status.AddColor)
|
||||
modColor = config.AsColor(c.styles.Style.Status.ModifyColor)
|
||||
errColor = config.AsColor(c.styles.Style.Status.ErrorColor)
|
||||
highlightColor = config.AsColor(c.styles.Style.Status.HighlightColor)
|
||||
completedColor = config.AsColor(c.styles.Style.Status.CompletedColor)
|
||||
}
|
||||
|
|
@ -3,19 +3,20 @@ package views
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/tview"
|
||||
)
|
||||
|
||||
type crumbsView struct {
|
||||
*tview.TextView
|
||||
|
||||
app *appView
|
||||
styles *config.Styles
|
||||
}
|
||||
|
||||
func newCrumbsView(app *appView) *crumbsView {
|
||||
v := crumbsView{app: app, TextView: tview.NewTextView()}
|
||||
func newCrumbsView(styles *config.Styles) *crumbsView {
|
||||
v := crumbsView{styles: styles, TextView: tview.NewTextView()}
|
||||
{
|
||||
v.SetBackgroundColor(app.styles.BgColor())
|
||||
v.SetBackgroundColor(styles.BgColor())
|
||||
v.SetTextAlign(tview.AlignLeft)
|
||||
v.SetBorderPadding(0, 0, 1, 1)
|
||||
v.SetDynamicColors(true)
|
||||
|
|
@ -26,14 +27,14 @@ func newCrumbsView(app *appView) *crumbsView {
|
|||
|
||||
func (v *crumbsView) update(crumbs []string) {
|
||||
v.Clear()
|
||||
last, bgColor := len(crumbs)-1, v.app.styles.Style.Crumb.BgColor
|
||||
last, bgColor := len(crumbs)-1, v.styles.Style.Crumb.BgColor
|
||||
for i, c := range crumbs {
|
||||
if i == last {
|
||||
bgColor = v.app.styles.Style.Crumb.ActiveColor
|
||||
bgColor = v.styles.Style.Crumb.ActiveColor
|
||||
}
|
||||
fmt.Fprintf(v, "[%s:%s:b] <%s> [-:%s:-] ",
|
||||
v.app.styles.Style.Crumb.FgColor,
|
||||
v.styles.Style.Crumb.FgColor,
|
||||
bgColor, c,
|
||||
v.app.styles.Style.BgColor)
|
||||
v.styles.Style.BgColor)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewCrumbs(t *testing.T) {
|
||||
defaults, _ := config.NewStyles("")
|
||||
v := newCrumbsView(defaults)
|
||||
v.update([]string{"blee", "duh"})
|
||||
|
||||
assert.Equal(t, "[black:aqua:b] <blee> [-:black:-] [black:orange:b] <duh> [-:black:-] \n", v.GetText(false))
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ func newDetailsView(app *appView, backFn actionHandler) *detailsView {
|
|||
v.SetInputCapture(v.keyboard)
|
||||
v.cmdBuff = newCmdBuff('/')
|
||||
{
|
||||
v.cmdBuff.addListener(app.cmdView)
|
||||
v.cmdBuff.addListener(app.cmd())
|
||||
v.cmdBuff.reset()
|
||||
}
|
||||
v.SetChangedFunc(func() {
|
||||
|
|
@ -104,7 +104,7 @@ func (v *detailsView) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (v *detailsView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !v.app.cmdView.inCmdMode() {
|
||||
if !v.app.inCmdMode() {
|
||||
v.cmdBuff.setActive(true)
|
||||
v.cmdBuff.clear()
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -22,16 +22,15 @@ type helpView struct {
|
|||
actions keyActions
|
||||
}
|
||||
|
||||
func newHelpView(app *appView) *helpView {
|
||||
func newHelpView(app *appView, current igniter) *helpView {
|
||||
v := helpView{TextView: tview.NewTextView(), app: app, actions: make(keyActions)}
|
||||
{
|
||||
v.SetTextColor(tcell.ColorAqua)
|
||||
v.SetBorder(true)
|
||||
v.SetBorderPadding(0, 0, 1, 1)
|
||||
v.SetDynamicColors(true)
|
||||
v.SetInputCapture(v.keyboard)
|
||||
v.current = app.content.GetPrimitive("main").(igniter)
|
||||
}
|
||||
v.SetTextColor(tcell.ColorAqua)
|
||||
v.SetBorder(true)
|
||||
v.SetBorderPadding(0, 0, 1, 1)
|
||||
v.SetDynamicColors(true)
|
||||
v.SetInputCapture(v.keyboard)
|
||||
v.current = current
|
||||
|
||||
v.actions[tcell.KeyEsc] = newKeyAction("Back", v.backCmd, true)
|
||||
v.actions[tcell.KeyEnter] = newKeyAction("Back", v.backCmd, false)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type ks struct{}
|
||||
|
||||
func (k ks) CurrentContextName() (string, error) {
|
||||
return "test", nil
|
||||
}
|
||||
|
||||
func (k ks) CurrentClusterName() (string, error) {
|
||||
return "test", nil
|
||||
}
|
||||
|
||||
func (k ks) CurrentNamespaceName() (string, error) {
|
||||
return "test", nil
|
||||
}
|
||||
|
||||
func (k ks) ClusterNames() ([]string, error) {
|
||||
return []string{"test"}, nil
|
||||
}
|
||||
|
||||
func (k ks) NamespaceNames(nn []v1.Namespace) []string {
|
||||
return []string{"test"}
|
||||
}
|
||||
|
||||
func newNS(n string) v1.Namespace {
|
||||
return v1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
||||
Name: n,
|
||||
}}
|
||||
}
|
||||
|
||||
func TestNewHelpView(t *testing.T) {
|
||||
cfg := config.NewConfig(ks{})
|
||||
a := NewApp(cfg)
|
||||
v := newHelpView(a, 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, e, v.GetText(true))
|
||||
assert.Equal(t, "Help", v.getTitle())
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -33,6 +34,12 @@ func stripPort(p string) string {
|
|||
return p
|
||||
}
|
||||
|
||||
// Namespaced converts an fqn resource name to ns and name.
|
||||
func namespaced(n string) (string, string) {
|
||||
ns, po := path.Split(n)
|
||||
return strings.Trim(ns, "/"), po
|
||||
}
|
||||
|
||||
// ContainerID computes container ID based on ns/po/co.
|
||||
func containerID(path, co string) string {
|
||||
ns, n := namespaced(path)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,37 @@ func init() {
|
|||
zerolog.SetGlobalLevel(zerolog.Disabled)
|
||||
}
|
||||
|
||||
func TestIsTCPPort(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
p string
|
||||
e bool
|
||||
}{
|
||||
"tcp": {"80╱TCP", true},
|
||||
"udp": {"80╱UDP", false},
|
||||
}
|
||||
|
||||
for k, u := range uu {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, isTCPPort(u.p))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFQN(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
ns, n, e string
|
||||
}{
|
||||
"fullFQN": {"blee", "fred", "blee/fred"},
|
||||
"allNS": {"", "fred", "fred"},
|
||||
}
|
||||
|
||||
for k, u := range uu {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, fqn(u.ns, u.n))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeltas(t *testing.T) {
|
||||
uu := []struct {
|
||||
s1, s2, e string
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ type logView struct {
|
|||
*tview.Flex
|
||||
|
||||
app *appView
|
||||
logs *detailsView
|
||||
backFn actionHandler
|
||||
logs *detailsView
|
||||
status *statusView
|
||||
ansiWriter io.Writer
|
||||
autoScroll int32
|
||||
|
|
@ -46,7 +46,7 @@ func newLogView(title string, app *appView, backFn actionHandler) *logView {
|
|||
v.logs.SetMaxBuffer(app.config.K9s.LogBufferSize)
|
||||
}
|
||||
v.ansiWriter = tview.ANSIWriter(v.logs)
|
||||
v.status = newStatusView(app)
|
||||
v.status = newStatusView(app.styles)
|
||||
v.SetDirection(tview.FlexRow)
|
||||
v.AddItem(v.status, 1, 1, false)
|
||||
v.AddItem(v.logs, 0, 1, true)
|
||||
|
|
@ -88,11 +88,6 @@ func (v *logView) logLine(line string) {
|
|||
fmt.Fprintln(v.ansiWriter, tview.Escape(line))
|
||||
}
|
||||
|
||||
func (v *logView) log(lines fmt.Stringer) {
|
||||
v.logs.Clear()
|
||||
fmt.Fprintln(v.ansiWriter, lines.String())
|
||||
}
|
||||
|
||||
func (v *logView) flush(index int, buff []string) {
|
||||
if index == 0 {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ package views
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
|
@ -22,3 +25,58 @@ func TestAnsi(t *testing.T) {
|
|||
fmt.Fprintf(aw, s)
|
||||
assert.Equal(t, s+"\n", v.GetText(false))
|
||||
}
|
||||
|
||||
func TestLogViewFlush(t *testing.T) {
|
||||
v := newLogView("Logs", NewApp(config.NewConfig(ks{})), nil)
|
||||
v.flush(2, []string{"blee", "bozo"})
|
||||
|
||||
v.toggleScrollCmd(nil)
|
||||
assert.Equal(t, "blee\nbozo\n", v.logs.GetText(true))
|
||||
assert.Equal(t, " Autoscroll: Off ", v.status.GetText(true))
|
||||
v.toggleScrollCmd(nil)
|
||||
assert.Equal(t, " Autoscroll: On ", v.status.GetText(true))
|
||||
}
|
||||
|
||||
func TestLogViewSave(t *testing.T) {
|
||||
v := newLogView("Logs", NewApp(config.NewConfig(ks{})), nil)
|
||||
v.flush(2, []string{"blee", "bozo"})
|
||||
v.path = "k9s-test"
|
||||
dir := filepath.Join(config.K9sDumpDir, v.app.config.K9s.CurrentCluster)
|
||||
c1, _ := ioutil.ReadDir(dir)
|
||||
v.saveCmd(nil)
|
||||
c2, _ := ioutil.ReadDir(dir)
|
||||
assert.Equal(t, len(c2), len(c1)+1)
|
||||
}
|
||||
|
||||
func TestLogViewNav(t *testing.T) {
|
||||
v := newLogView("Logs", NewApp(config.NewConfig(ks{})), nil)
|
||||
var buff []string
|
||||
v.autoScroll = 1
|
||||
for i := 0; i < 100; i++ {
|
||||
buff = append(buff, fmt.Sprintf("line-%d\n", i))
|
||||
}
|
||||
v.flush(100, buff)
|
||||
|
||||
v.topCmd(nil)
|
||||
r, _ := v.logs.GetScrollOffset()
|
||||
assert.Equal(t, 0, r)
|
||||
v.pageDownCmd(nil)
|
||||
r, _ = v.logs.GetScrollOffset()
|
||||
assert.Equal(t, 0, r)
|
||||
v.pageUpCmd(nil)
|
||||
r, _ = v.logs.GetScrollOffset()
|
||||
assert.Equal(t, 0, r)
|
||||
v.bottomCmd(nil)
|
||||
r, _ = v.logs.GetScrollOffset()
|
||||
assert.Equal(t, 0, r)
|
||||
}
|
||||
|
||||
func TestLogViewClear(t *testing.T) {
|
||||
v := newLogView("Logs", NewApp(config.NewConfig(ks{})), nil)
|
||||
v.flush(2, []string{"blee", "bozo"})
|
||||
|
||||
v.toggleScrollCmd(nil)
|
||||
assert.Equal(t, "blee\nbozo\n", v.logs.GetText(true))
|
||||
v.clearCmd(nil)
|
||||
assert.Equal(t, "", v.logs.GetText(true))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,28 +10,28 @@ import (
|
|||
type logoView struct {
|
||||
*tview.Flex
|
||||
logo, status *tview.TextView
|
||||
app *appView
|
||||
styles *config.Styles
|
||||
}
|
||||
|
||||
func newLogoView(app *appView) *logoView {
|
||||
func newLogoView(styles *config.Styles) *logoView {
|
||||
v := logoView{
|
||||
Flex: tview.NewFlex(),
|
||||
logo: logo(),
|
||||
status: status(),
|
||||
app: app,
|
||||
styles: styles,
|
||||
}
|
||||
v.SetDirection(tview.FlexRow)
|
||||
v.AddItem(v.logo, 0, 6, false)
|
||||
v.AddItem(v.status, 0, 1, false)
|
||||
v.refreshLogo(app.styles.Style.LogoColor)
|
||||
v.refreshLogo(styles.Style.LogoColor)
|
||||
|
||||
return &v
|
||||
}
|
||||
|
||||
func (v *logoView) reset() {
|
||||
v.status.Clear()
|
||||
v.status.SetBackgroundColor(v.app.styles.BgColor())
|
||||
v.refreshLogo(v.app.styles.Style.LogoColor)
|
||||
v.status.SetBackgroundColor(v.styles.BgColor())
|
||||
v.refreshLogo(v.styles.Style.LogoColor)
|
||||
}
|
||||
|
||||
func (v *logoView) err(msg string) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewLogoView(t *testing.T) {
|
||||
defaults, _ := config.NewStyles("")
|
||||
v := newLogoView(defaults)
|
||||
v.reset()
|
||||
|
||||
const elogo = "[orange::b] ____ __.________ \n[orange::b]| |/ _/ __ \\______\n[orange::b]| < \\____ / ___/\n[orange::b]| | \\ / /\\___ \\ \n[orange::b]|____|__ \\ /____//____ >\n[orange::b] \\/ \\/ \n"
|
||||
assert.Equal(t, elogo, v.logo.GetText(false))
|
||||
assert.Equal(t, "", v.status.GetText(false))
|
||||
}
|
||||
|
||||
func TestLogoStatus(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
logo, msg, e string
|
||||
}{
|
||||
"info": {
|
||||
"[green::b] ____ __.________ \n[green::b]| |/ _/ __ \\______\n[green::b]| < \\____ / ___/\n[green::b]| | \\ / /\\___ \\ \n[green::b]|____|__ \\ /____//____ >\n[green::b] \\/ \\/ \n",
|
||||
"blee",
|
||||
"[white::b]blee\n",
|
||||
},
|
||||
"warn": {
|
||||
"[mediumvioletred::b] ____ __.________ \n[mediumvioletred::b]| |/ _/ __ \\______\n[mediumvioletred::b]| < \\____ / ___/\n[mediumvioletred::b]| | \\ / /\\___ \\ \n[mediumvioletred::b]|____|__ \\ /____//____ >\n[mediumvioletred::b] \\/ \\/ \n",
|
||||
"blee",
|
||||
"[white::b]blee\n",
|
||||
},
|
||||
"err": {
|
||||
"[red::b] ____ __.________ \n[red::b]| |/ _/ __ \\______\n[red::b]| < \\____ / ___/\n[red::b]| | \\ / /\\___ \\ \n[red::b]|____|__ \\ /____//____ >\n[red::b] \\/ \\/ \n",
|
||||
"blee",
|
||||
"[white::b]blee\n",
|
||||
},
|
||||
}
|
||||
|
||||
defaults, _ := config.NewStyles("")
|
||||
v := newLogoView(defaults)
|
||||
for k, u := range uu {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
switch k {
|
||||
case "info":
|
||||
v.info(u.msg)
|
||||
case "warn":
|
||||
v.warn(u.msg)
|
||||
case "err":
|
||||
v.err(u.msg)
|
||||
}
|
||||
assert.Equal(t, u.logo, v.logo.GetText(false))
|
||||
assert.Equal(t, u.e, v.status.GetText(false))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
|
|
@ -100,12 +101,12 @@ func (a keyActions) toHints() hints {
|
|||
type menuView struct {
|
||||
*tview.Table
|
||||
|
||||
app *appView
|
||||
styles *config.Styles
|
||||
}
|
||||
|
||||
func newMenuView(app *appView) *menuView {
|
||||
v := menuView{Table: tview.NewTable(), app: app}
|
||||
v.SetBackgroundColor(app.styles.BgColor())
|
||||
func newMenuView(styles *config.Styles) *menuView {
|
||||
v := menuView{Table: tview.NewTable(), styles: styles}
|
||||
v.SetBackgroundColor(styles.BgColor())
|
||||
|
||||
return &v
|
||||
}
|
||||
|
|
@ -120,7 +121,7 @@ func (v *menuView) populateMenu(hh hints) {
|
|||
continue
|
||||
}
|
||||
c := tview.NewTableCell(t[row][col])
|
||||
c.SetBackgroundColor(v.app.styles.BgColor())
|
||||
c.SetBackgroundColor(v.styles.BgColor())
|
||||
v.SetCell(row, col, c)
|
||||
}
|
||||
}
|
||||
|
|
@ -177,16 +178,16 @@ func (*menuView) toMnemonic(s string) string {
|
|||
func (v *menuView) formatMenu(h hint, size int) string {
|
||||
i, err := strconv.Atoi(h.mnemonic)
|
||||
if err == nil {
|
||||
fmat := strings.Replace(menuIndexFmt, "[key", "["+v.app.styles.Style.Menu.NumKeyColor, 1)
|
||||
fmat = strings.Replace(fmat, ":bg:", ":"+v.app.styles.Style.Title.BgColor+":", -1)
|
||||
fmat = strings.Replace(fmat, "[fg", "["+v.app.styles.Style.Menu.FgColor, 1)
|
||||
fmat := strings.Replace(menuIndexFmt, "[key", "["+v.styles.Style.Menu.NumKeyColor, 1)
|
||||
fmat = strings.Replace(fmat, ":bg:", ":"+v.styles.Style.Title.BgColor+":", -1)
|
||||
fmat = strings.Replace(fmat, "[fg", "["+v.styles.Style.Menu.FgColor, 1)
|
||||
return fmt.Sprintf(fmat, i, resource.Truncate(h.description, 14))
|
||||
}
|
||||
|
||||
menuFmt := " [key:bg:b]%-" + strconv.Itoa(size+2) + "s [fg:bg:d]%s "
|
||||
fmat := strings.Replace(menuFmt, "[key", "["+v.app.styles.Style.Menu.KeyColor, 1)
|
||||
fmat = strings.Replace(fmat, "[fg", "["+v.app.styles.Style.Menu.FgColor, 1)
|
||||
fmat = strings.Replace(fmat, ":bg:", ":"+v.app.styles.Style.Title.BgColor+":", -1)
|
||||
fmat := strings.Replace(menuFmt, "[key", "["+v.styles.Style.Menu.KeyColor, 1)
|
||||
fmat = strings.Replace(fmat, "[fg", "["+v.styles.Style.Menu.FgColor, 1)
|
||||
fmat = strings.Replace(fmat, ":bg:", ":"+v.styles.Style.Title.BgColor+":", -1)
|
||||
return fmt.Sprintf(fmat, v.toMnemonic(h.mnemonic), h.description)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewMenuView(t *testing.T) {
|
||||
defaults, _ := config.NewStyles("")
|
||||
v := newMenuView(defaults)
|
||||
v.populateMenu(hints{
|
||||
{"a", "bleeA"},
|
||||
{"b", "bleeB"},
|
||||
{"0", "zero"},
|
||||
})
|
||||
|
||||
assert.Equal(t, " [fuchsia:black:b]<0> [white:black:d]zero ", v.GetCell(0, 0).Text)
|
||||
assert.Equal(t, " [dodgerblue:black:b]<a> [white:black:d]bleeA ", v.GetCell(0, 1).Text)
|
||||
assert.Equal(t, " [dodgerblue:black:b]<b> [white:black:d]bleeB ", v.GetCell(1, 1).Text)
|
||||
}
|
||||
|
||||
func TestKeyActions(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
aa keyActions
|
||||
e hints
|
||||
}{
|
||||
"a": {
|
||||
aa: keyActions{
|
||||
KeyB: newKeyAction("bleeB", nil, true),
|
||||
KeyA: newKeyAction("bleeA", nil, true),
|
||||
tcell.Key(Key0): newKeyAction("zero", nil, true),
|
||||
tcell.Key(Key1): newKeyAction("one", nil, false),
|
||||
},
|
||||
e: hints{
|
||||
{"0", "zero"},
|
||||
{"a", "bleeA"},
|
||||
{"b", "bleeB"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for k, u := range uu {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, u.aa.toHints())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Code generated by pegomock. DO NOT EDIT.
|
||||
// Source: github.com/derailed/k9s/internal/k8s (interfaces: Connection)
|
||||
// Source: github.com/derailed/k9s/internal/config (interfaces: Connection)
|
||||
|
||||
package views
|
||||
|
||||
|
|
@ -24,19 +24,23 @@ func NewMockConnection() *MockConnection {
|
|||
return &MockConnection{fail: pegomock.GlobalFailHandler}
|
||||
}
|
||||
|
||||
func (mock *MockConnection) CanIAccess(_param0 string, _param1 string, _param2 string, _param3 []string) bool {
|
||||
func (mock *MockConnection) CanIAccess(_param0 string, _param1 string, _param2 string, _param3 []string) (bool, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{_param0, _param1, _param2, _param3}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("CanIAccess", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()})
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("CanIAccess", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 bool
|
||||
var ret1 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(bool)
|
||||
}
|
||||
if result[1] != nil {
|
||||
ret1 = result[1].(error)
|
||||
}
|
||||
}
|
||||
return ret0
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockConnection) Config() *k8s.Config {
|
||||
|
|
@ -239,14 +243,15 @@ func (mock *MockConnection) ServerVersion() (*version.Info, error) {
|
|||
return ret0, ret1
|
||||
}
|
||||
|
||||
func (mock *MockConnection) SupportsRes(_param0 string, _param1 []string) (string, bool) {
|
||||
func (mock *MockConnection) SupportsRes(_param0 string, _param1 []string) (string, bool, error) {
|
||||
if mock == nil {
|
||||
panic("mock must not be nil. Use myMock := NewMockConnection().")
|
||||
}
|
||||
params := []pegomock.Param{_param0, _param1}
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("SupportsRes", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*bool)(nil)).Elem()})
|
||||
result := pegomock.GetGenericMockFrom(mock).Invoke("SupportsRes", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
|
||||
var ret0 string
|
||||
var ret1 bool
|
||||
var ret2 error
|
||||
if len(result) != 0 {
|
||||
if result[0] != nil {
|
||||
ret0 = result[0].(string)
|
||||
|
|
@ -254,8 +259,11 @@ func (mock *MockConnection) SupportsRes(_param0 string, _param1 []string) (strin
|
|||
if result[1] != nil {
|
||||
ret1 = result[1].(bool)
|
||||
}
|
||||
if result[2] != nil {
|
||||
ret2 = result[2].(error)
|
||||
}
|
||||
}
|
||||
return ret0, ret1
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
func (mock *MockConnection) SupportsResource(_param0 string) bool {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ func helpCmds(c k8s.Connection) map[string]resCmd {
|
|||
|
||||
func allCRDs(c k8s.Connection) map[string]k8s.APIGroup {
|
||||
m := map[string]k8s.APIGroup{}
|
||||
if c == nil {
|
||||
return m
|
||||
}
|
||||
|
||||
crds, _ := resource.NewCustomResourceDefinitionList(c, resource.AllNamespaces).
|
||||
Resource().
|
||||
|
|
@ -310,6 +313,9 @@ func resourceViews(c k8s.Connection) map[string]resCmd {
|
|||
},
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
return cmds
|
||||
}
|
||||
rev, ok, err := c.SupportsRes("autoscaling", []string{"v1", "v2beta1", "v2beta2"})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Checking HPA")
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ func (v *resourceView) init(ctx context.Context, ns string) {
|
|||
}
|
||||
|
||||
v.update(vctx)
|
||||
v.app.clusterInfoView.refresh()
|
||||
v.app.clusterInfoRefresh()
|
||||
v.refresh()
|
||||
if tv, ok := v.CurrentPage().Item.(*tableView); ok {
|
||||
r, _ := tv.GetSelection()
|
||||
|
|
@ -275,7 +275,7 @@ func (v *resourceView) dismissModal() {
|
|||
}
|
||||
|
||||
func (v *resourceView) defaultEnter(ns, resource, selection string) {
|
||||
yaml, err := v.list.Resource().Describe(v.title, selection, v.app.flags)
|
||||
yaml, err := v.list.Resource().Describe(v.title, selection)
|
||||
if err != nil {
|
||||
v.app.flash().errf("Describe command failed %s", err)
|
||||
log.Warn().Msgf("Describe %v", err.Error())
|
||||
|
|
@ -446,11 +446,6 @@ func (v *resourceView) rowSelected() bool {
|
|||
return v.selectedItem != noSelection
|
||||
}
|
||||
|
||||
func namespaced(n string) (string, string) {
|
||||
ns, po := path.Split(n)
|
||||
return strings.Trim(ns, "/"), po
|
||||
}
|
||||
|
||||
func (v *resourceView) refreshActions() {
|
||||
if v.list.Access(resource.NamespaceAccess) {
|
||||
v.namespaces = make(map[int]string, config.MaxFavoritesNS)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package views
|
||||
|
||||
// import (
|
||||
// "context"
|
||||
// "testing"
|
||||
|
||||
// "github.com/derailed/k9s/internal/config"
|
||||
// "github.com/derailed/k9s/internal/resource"
|
||||
// )
|
||||
|
||||
// func TestNewResource(t *testing.T) {
|
||||
// mc := NewMockConnection()
|
||||
// mk := NewMockKubeSettings()
|
||||
|
||||
// c := config.NewConfig(mk)
|
||||
// c.SetConnection(mc)
|
||||
// a := NewApp(c)
|
||||
// l := resource.NewPodList(nil, "")
|
||||
// v := newResourceView("fred", a, l)
|
||||
|
||||
// ctx, _ := context.WithCancel(context.Background())
|
||||
// v.init(ctx, "")
|
||||
// }
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type (
|
||||
keyHandler interface {
|
||||
keyboard(evt *tcell.EventKey) *tcell.EventKey
|
||||
}
|
||||
|
||||
actionsFn func(keyActions)
|
||||
|
||||
shellView struct {
|
||||
*tview.Application
|
||||
configurator
|
||||
|
||||
actions keyActions
|
||||
pages *tview.Pages
|
||||
content *tview.Pages
|
||||
views map[string]tview.Primitive
|
||||
}
|
||||
)
|
||||
|
||||
func newShellView() *shellView {
|
||||
return &shellView{
|
||||
Application: tview.NewApplication(),
|
||||
actions: make(keyActions),
|
||||
pages: tview.NewPages(),
|
||||
content: tview.NewPages(),
|
||||
views: make(map[string]tview.Primitive),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
|
@ -58,6 +57,10 @@ func less(asc bool, c1, c2 string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
if o, ok := isIntegerSort(asc, c1, c2); ok {
|
||||
return o
|
||||
}
|
||||
|
||||
if o, ok := isMetricSort(asc, c1, c2); ok {
|
||||
return o
|
||||
}
|
||||
|
|
@ -66,10 +69,6 @@ func less(asc bool, c1, c2 string) bool {
|
|||
return o
|
||||
}
|
||||
|
||||
if o, ok := isIntegerSort(asc, c1, c2); ok {
|
||||
return o
|
||||
}
|
||||
|
||||
b := sortorder.NaturalLess(c1, c2)
|
||||
if asc {
|
||||
return b
|
||||
|
|
@ -104,26 +103,18 @@ func isMetricSort(asc bool, c1, c2 string) (bool, bool) {
|
|||
}
|
||||
|
||||
func isIntegerSort(asc bool, c1, c2 string) (bool, bool) {
|
||||
n1, err := strconv.Atoi(c1)
|
||||
if err != nil {
|
||||
n1, err1 := strconv.Atoi(c1)
|
||||
n2, err2 := strconv.Atoi(c2)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, false
|
||||
}
|
||||
n2, _ := strconv.Atoi(c2)
|
||||
|
||||
if asc {
|
||||
return n1 <= n2, true
|
||||
}
|
||||
return n1 > n2, true
|
||||
}
|
||||
|
||||
var metricRX = regexp.MustCompile(`\A(\d+)(m|Mi)\z`)
|
||||
|
||||
func isMetric(s string) (string, bool) {
|
||||
if m := metricRX.FindStringSubmatch(s); len(m) == 3 {
|
||||
return m[1], true
|
||||
}
|
||||
return s, false
|
||||
}
|
||||
|
||||
func isDuration(s string) (time.Duration, bool) {
|
||||
d, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
|
@ -36,44 +37,39 @@ var Logo = []string{
|
|||
// Splash screen definition
|
||||
type splashView struct {
|
||||
*tview.Flex
|
||||
|
||||
app *appView
|
||||
}
|
||||
|
||||
// NewSplash instantiates a new splash screen with product and company info.
|
||||
func newSplash(app *appView) *splashView {
|
||||
v := splashView{Flex: tview.NewFlex(), app: app}
|
||||
func newSplash(styles *config.Styles, version string) *splashView {
|
||||
v := splashView{Flex: tview.NewFlex()}
|
||||
|
||||
logo := tview.NewTextView()
|
||||
{
|
||||
logo.SetDynamicColors(true)
|
||||
logo.SetBackgroundColor(tcell.ColorDefault)
|
||||
logo.SetTextAlign(tview.AlignCenter)
|
||||
}
|
||||
v.layoutLogo(logo)
|
||||
logo.SetDynamicColors(true)
|
||||
logo.SetBackgroundColor(tcell.ColorDefault)
|
||||
logo.SetTextAlign(tview.AlignCenter)
|
||||
v.layoutLogo(logo, styles)
|
||||
|
||||
vers := tview.NewTextView()
|
||||
{
|
||||
vers.SetDynamicColors(true)
|
||||
vers.SetBackgroundColor(tcell.ColorDefault)
|
||||
vers.SetTextAlign(tview.AlignCenter)
|
||||
}
|
||||
v.layoutRev(vers, app.version)
|
||||
vers.SetDynamicColors(true)
|
||||
vers.SetBackgroundColor(tcell.ColorDefault)
|
||||
vers.SetTextAlign(tview.AlignCenter)
|
||||
v.layoutRev(vers, version, styles)
|
||||
|
||||
v.SetDirection(tview.FlexRow)
|
||||
v.AddItem(logo, 10, 1, false)
|
||||
v.AddItem(vers, 1, 1, false)
|
||||
|
||||
return &v
|
||||
}
|
||||
|
||||
func (v *splashView) layoutLogo(t *tview.TextView) {
|
||||
logo := strings.Join(Logo, fmt.Sprintf("\n[%s::b]", v.app.styles.Style.LogoColor))
|
||||
func (v *splashView) layoutLogo(t *tview.TextView, styles *config.Styles) {
|
||||
logo := strings.Join(Logo, fmt.Sprintf("\n[%s::b]", styles.Style.LogoColor))
|
||||
fmt.Fprintf(t, "%s[%s::b]%s\n",
|
||||
strings.Repeat("\n", 2),
|
||||
v.app.styles.Style.LogoColor,
|
||||
styles.Style.LogoColor,
|
||||
logo)
|
||||
}
|
||||
|
||||
func (v *splashView) layoutRev(t *tview.TextView, rev string) {
|
||||
fmt.Fprintf(t, "[%s::b]Revision [red::b]%s", v.app.styles.Style.FgColor, rev)
|
||||
func (v *splashView) layoutRev(t *tview.TextView, rev string, styles *config.Styles) {
|
||||
fmt.Fprintf(t, "[%s::b]Revision [red::b]%s", styles.Style.FgColor, rev)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewSplash(t *testing.T) {
|
||||
defaults, _ := config.NewStyles("")
|
||||
s := newSplash(defaults, "bozo")
|
||||
|
||||
x, y, w, h := s.GetRect()
|
||||
assert.Equal(t, 0, x)
|
||||
assert.Equal(t, 0, y)
|
||||
assert.Equal(t, 15, w)
|
||||
assert.Equal(t, 10, h)
|
||||
}
|
||||
|
|
@ -10,13 +10,13 @@ import (
|
|||
type statusView struct {
|
||||
*tview.TextView
|
||||
|
||||
app *appView
|
||||
styles *config.Styles
|
||||
}
|
||||
|
||||
func newStatusView(app *appView) *statusView {
|
||||
v := statusView{app: app, TextView: tview.NewTextView()}
|
||||
func newStatusView(styles *config.Styles) *statusView {
|
||||
v := statusView{styles: styles, TextView: tview.NewTextView()}
|
||||
{
|
||||
v.SetBackgroundColor(config.AsColor(app.styles.Style.Log.BgColor))
|
||||
v.SetBackgroundColor(config.AsColor(styles.Style.Log.BgColor))
|
||||
v.SetTextAlign(tview.AlignRight)
|
||||
v.SetDynamicColors(true)
|
||||
}
|
||||
|
|
@ -25,14 +25,14 @@ func newStatusView(app *appView) *statusView {
|
|||
|
||||
func (v *statusView) update(status []string) {
|
||||
v.Clear()
|
||||
last, bgColor := len(status)-1, v.app.styles.Style.Crumb.BgColor
|
||||
last, bgColor := len(status)-1, v.styles.Style.Crumb.BgColor
|
||||
for i, c := range status {
|
||||
if i == last {
|
||||
bgColor = v.app.styles.Style.Crumb.ActiveColor
|
||||
bgColor = v.styles.Style.Crumb.ActiveColor
|
||||
}
|
||||
fmt.Fprintf(v, "[%s:%s:b] %s [-:%s:-] ",
|
||||
v.app.styles.Style.Crumb.FgColor,
|
||||
v.styles.Style.Crumb.FgColor,
|
||||
bgColor, c,
|
||||
v.app.styles.Style.BgColor)
|
||||
v.styles.Style.BgColor)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewStatus(t *testing.T) {
|
||||
defaults, _ := config.NewStyles("")
|
||||
v := newStatusView(defaults)
|
||||
v.update([]string{"blee", "duh"})
|
||||
|
||||
assert.Equal(t, "[black:aqua:b] blee [-:black:-] [black:orange:b] duh [-:black:-] \n", v.GetText(false))
|
||||
}
|
||||
|
|
@ -81,12 +81,10 @@ func (v *subjectView) init(c context.Context, _ string) {
|
|||
v.app.SetFocus(v)
|
||||
}
|
||||
|
||||
func (v *subjectView) setExtraActionsFn(f actionsFn) {
|
||||
}
|
||||
|
||||
func (v *subjectView) setColorerFn(f colorerFn) {}
|
||||
func (v *subjectView) setEnterFn(f enterFn) {}
|
||||
func (v *subjectView) setDecorateFn(f decorateFn) {}
|
||||
func (v *subjectView) setExtraActionsFn(f actionsFn) {}
|
||||
func (v *subjectView) setColorerFn(f colorerFn) {}
|
||||
func (v *subjectView) setEnterFn(f enterFn) {}
|
||||
func (v *subjectView) setDecorateFn(f decorateFn) {}
|
||||
|
||||
func (v *subjectView) bindKeys() {
|
||||
// No time data or ns
|
||||
|
|
@ -97,7 +95,6 @@ func (v *subjectView) bindKeys() {
|
|||
v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.resetCmd, false)
|
||||
v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd, false)
|
||||
v.actions[KeyP] = newKeyAction("Previous", v.app.prevCmd, false)
|
||||
|
||||
v.actions[KeyShiftK] = newKeyAction("Sort Kind", v.sortColCmd(1), true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ func newTableView(app *appView, title string) *tableView {
|
|||
v.SetBorderFocusColor(config.AsColor(app.styles.Style.Border.FocusColor))
|
||||
v.SetBorderAttributes(tcell.AttrBold)
|
||||
v.SetBorderPadding(0, 0, 1, 1)
|
||||
v.cmdBuff.addListener(app.cmdView)
|
||||
v.cmdBuff.addListener(app.cmd())
|
||||
v.cmdBuff.reset()
|
||||
v.SetSelectable(true, false)
|
||||
v.SetSelectedStyle(
|
||||
|
|
@ -265,7 +265,7 @@ func (v *tableView) sortInvertCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (v *tableView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if v.app.cmdView.inCmdMode() {
|
||||
if v.app.inCmdMode() {
|
||||
return evt
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,107 +0,0 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const newLogColor = "greenyellow"
|
||||
|
||||
type (
|
||||
logBuffer struct {
|
||||
capacity int
|
||||
decorate bool
|
||||
modified bool
|
||||
head *logEntry
|
||||
current *logEntry
|
||||
rx *regexp.Regexp
|
||||
}
|
||||
|
||||
logEntry struct {
|
||||
line string
|
||||
next *logEntry
|
||||
}
|
||||
)
|
||||
|
||||
func newLogBuffer(c int, f bool) *logBuffer {
|
||||
return &logBuffer{capacity: c, decorate: f, rx: regexp.MustCompile(`\[\w*\:\:\]`)}
|
||||
}
|
||||
|
||||
func (b *logBuffer) clear() {
|
||||
b.head, b.current = nil, nil
|
||||
}
|
||||
|
||||
func (b *logBuffer) add(line string) {
|
||||
b.modified = true
|
||||
if b.decorate {
|
||||
line = b.decorateLine(line)
|
||||
}
|
||||
n := logEntry{line: line}
|
||||
if b.head == nil {
|
||||
b.head = &n
|
||||
b.current = b.head
|
||||
return
|
||||
}
|
||||
|
||||
if b.full() {
|
||||
b.head = b.head.next
|
||||
}
|
||||
b.current.next = &n
|
||||
b.current = &n
|
||||
}
|
||||
|
||||
func (b *logBuffer) full() bool {
|
||||
return b.length() == b.capacity
|
||||
}
|
||||
|
||||
func (b *logBuffer) length() int {
|
||||
c, count := b.head, 0
|
||||
for c != nil {
|
||||
c = c.next
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (*logBuffer) decorateLine(l string) string {
|
||||
return l
|
||||
}
|
||||
|
||||
func (b *logBuffer) trimLine(l string) string {
|
||||
return b.rx.ReplaceAllString(l, "")
|
||||
}
|
||||
|
||||
func (b *logBuffer) cleanse() {
|
||||
if !b.modified {
|
||||
return
|
||||
}
|
||||
c := b.head
|
||||
for c != nil {
|
||||
c.line = b.trimLine(c.line)
|
||||
c = c.next
|
||||
}
|
||||
b.modified = true
|
||||
}
|
||||
|
||||
func (b *logBuffer) String() string {
|
||||
return strings.Join(b.lines(), "\n")
|
||||
}
|
||||
|
||||
func (b *logBuffer) lines() []string {
|
||||
out := make([]string, b.length())
|
||||
c := b.head
|
||||
for i := 0; c != nil; i++ {
|
||||
out[i] = c.line
|
||||
c = c.next
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (b *logBuffer) dump() {
|
||||
c := b.head
|
||||
for c != nil {
|
||||
fmt.Println(c.line)
|
||||
c = c.next
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLogBufferAdd(t *testing.T) {
|
||||
uu := []struct {
|
||||
lines []string
|
||||
expected []string
|
||||
}{
|
||||
{[]string{}, []string{}},
|
||||
{[]string{"l1"}, []string{"l1"}},
|
||||
{[]string{"l1", "l2"}, []string{"l1", "l2"}},
|
||||
{[]string{"l1", "l2", "l3"}, []string{"l2", "l3"}},
|
||||
{[]string{"l1", "l2", "l3", "l4"}, []string{"l3", "l4"}},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
b := newLogBuffer(2, false)
|
||||
for _, l := range u.lines {
|
||||
b.add(l)
|
||||
}
|
||||
|
||||
assert.Equal(t, len(u.expected), b.length())
|
||||
assert.Equal(t, u.expected, b.lines())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogBufferCleanse(t *testing.T) {
|
||||
b := newLogBuffer(2, true)
|
||||
ll := []string{"l1", "l2"}
|
||||
ee := []string{b.decorateLine("l1"), b.decorateLine("l2")}
|
||||
for _, l := range ll {
|
||||
b.add(l)
|
||||
}
|
||||
assert.Equal(t, ee, b.lines())
|
||||
b.cleanse()
|
||||
assert.Equal(t, ll, b.lines())
|
||||
}
|
||||
|
||||
func TestLogBufferDecorate(t *testing.T) {
|
||||
l := "hello k9s"
|
||||
var b *logBuffer
|
||||
assert.Equal(t, l, b.decorateLine(l))
|
||||
}
|
||||
|
||||
func TestLogBufferTrimLine(t *testing.T) {
|
||||
l := "hello k9s"
|
||||
dl := "[" + newLogColor + "::]" + l + "[::]"
|
||||
b := newLogBuffer(1, true)
|
||||
assert.Equal(t, l, b.trimLine(dl))
|
||||
}
|
||||
|
|
@ -41,7 +41,7 @@ func TestYaml(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
s, _ := config.NewStyles(config.K9sStylesFile)
|
||||
s, _ := config.NewStyles("skins/stock.yml")
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, colorizeYAML(s.Style, u.s))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue