checkpoint

mine
derailed 2019-11-14 18:28:23 -07:00
parent 2c57888bac
commit cf98f61ad6
29 changed files with 232 additions and 126 deletions

View File

@ -116,6 +116,18 @@ func (s *Stack) Pop() (Component, bool) {
return c, true
}
// Peek returns stack state.
func (s *Stack) Peek() []Component {
return s.components
}
// Reset clear out the stack.
func (s *Stack) Reset() {
for range s.components {
s.Pop()
}
}
// Empty returns true if the stack is empty.
func (s *Stack) Empty() bool {
return len(s.components) == 0

View File

@ -28,7 +28,7 @@ type (
func NewContainerList(c Connection, pod *v1.Pod) List {
return NewList(
"",
"co",
"coontainers",
NewContainer(c, pod),
0,
)

View File

@ -289,8 +289,7 @@ func (l *list) Reconcile(informer *wa.Informer, path *string) error {
ns = *path
}
items, err := l.load(informer, ns)
if err == nil {
if items, err := l.load(informer, ns); err == nil {
l.update(items)
return nil
}
@ -299,11 +298,11 @@ func (l *list) Reconcile(informer *wa.Informer, path *string) error {
LabelSelector: l.labelSelector,
FieldSelector: l.fieldSelector,
}
if items, err = l.resource.List(l.namespace, opts); err != nil {
if items, err := l.resource.List(l.namespace, opts); err == nil {
l.update(items)
} else {
return err
}
l.update(items)
return nil
}

View File

@ -28,7 +28,7 @@ type Node struct {
func NewNodeList(c Connection, _ string) List {
return NewList(
NotNamespaced,
"no",
"nodes",
NewNode(c),
ViewAccess|DescribeAccess,
)

View File

@ -27,7 +27,6 @@ func NewApp() *App {
Main: NewPages(),
cmdBuff: NewCmdBuff(':', CommandBuff),
}
a.RefreshStyles()
a.views = map[string]tview.Primitive{

View File

@ -41,8 +41,12 @@ func (v *Menu) StackPushed(c model.Component) {
v.HydrateMenu(c.Hints())
}
func (v *Menu) StackPopped(o, n model.Component) {
v.HydrateMenu(n.Hints())
func (v *Menu) StackPopped(o, top model.Component) {
if top != nil {
v.HydrateMenu(top.Hints())
} else {
v.Clear()
}
}
func (v *Menu) StackTop(t model.Component) {

View File

@ -1,6 +1,8 @@
package ui
import (
"fmt"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/tview"
"github.com/rs/zerolog/log"
@ -21,40 +23,39 @@ func NewPages() *Pages {
return &p
}
// Get fetch a page given its name.
func (p *Pages) get(n string) model.Component {
if comp, ok := p.GetPrimitive(n).(model.Component); ok {
return comp
func (p *Pages) Show(c model.Component) {
p.SwitchToPage(componentID(c))
}
func (p *Pages) Current() model.Component {
c := p.CurrentPage()
if c == nil {
return nil
}
return nil
return c.Item.(model.Component)
}
// AddAndShow adds a new page and bring it to front.
func (p *Pages) addAndShow(c model.Component) {
p.add(c)
p.Show(c.Name())
p.Show(c)
}
// Add adds a new page.
func (p *Pages) add(c model.Component) {
p.AddPage(c.Name(), c, true, true)
p.AddPage(componentID(c), c, true, true)
}
// Delete removes a page.
func (p *Pages) delete(c model.Component) {
p.RemovePage(c.Name())
}
// Show brings a named page forward.
func (p *Pages) Show(n string) {
p.SwitchToPage(n)
p.RemovePage(componentID(c))
}
func (p *Pages) DumpPages() {
log.Debug().Msgf("Dumping Pages %p", p)
for i, n := range p.Stack.Flatten() {
log.Debug().Msgf("%d -- %s -- %#v", i, n, p.GetPrimitive(n))
for i, c := range p.Stack.Peek() {
log.Debug().Msgf("%d -- %s -- %#v", i, componentID(c), p.GetPrimitive(componentID(c)))
}
}
@ -72,5 +73,11 @@ func (p *Pages) StackTop(top model.Component) {
if top == nil {
return
}
p.Show(top.Name())
p.Show(top)
}
// Helpers...
func componentID(c model.Component) string {
return fmt.Sprintf("%s-%p", c.Name(), c)
}

View File

@ -60,7 +60,6 @@ func NewTable(title string) *Table {
}
func (t *Table) Init(ctx context.Context) {
log.Debug().Msgf("UI Table INIT %q", t.baseTitle)
t.styles = ctx.Value(KeyStyles).(*config.Styles)
t.SetFixed(1, 0)

View File

@ -83,6 +83,9 @@ func (a *App) ActiveView() model.Component {
}
func (a *App) PrevCmd(evt *tcell.EventKey) *tcell.EventKey {
log.Debug().Msgf("------ CONTENT PREVIOUS")
a.Content.DumpStack()
a.Content.DumpPages()
if !a.Content.IsLast() {
a.Content.Pop()
}
@ -227,9 +230,9 @@ func (a *App) switchNS(ns string) bool {
ns = resource.AllNamespaces
}
if ns == a.Config.ActiveNamespace() {
log.Debug().Msgf("Namespace did not change %s", ns)
return true
}
if err := a.Config.SetActiveNamespace(ns); err != nil {
log.Error().Err(err).Msg("Config Set NS failed!")
}
@ -248,6 +251,11 @@ func (a *App) switchCtx(ctx string, load bool) error {
if err != nil {
log.Info().Err(err).Msg("No namespace specified using all namespaces")
}
if a.stopCh != nil {
close(a.stopCh)
a.stopCh = nil
}
a.informer = nil
a.startInformer(ns)
a.Config.Reset()
if err := a.Config.Save(); err != nil {
@ -262,6 +270,12 @@ func (a *App) switchCtx(ctx string, load bool) error {
}
func (a *App) startInformer(ns string) bool {
// if informer watches all ns - don't start a new informer then.
if a.informer != nil && a.informer.Namespace == resource.AllNamespaces {
log.Debug().Msgf(">>>> Informer is already watching all namespaces. No restart needed ;)")
return true
}
if a.stopCh != nil {
close(a.stopCh)
a.stopCh = nil
@ -381,6 +395,7 @@ func (a *App) toggleHeaderCmd(evt *tcell.EventKey) *tcell.EventKey {
func (a *App) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.CmdBuff().IsActive() && !a.CmdBuff().Empty() {
a.Content.Stack.Reset()
a.gotoResource(a.GetCmd(), true)
a.ResetCmd()
return nil

View File

@ -100,7 +100,6 @@ func (b *Bench) refresh() {
func (b *Bench) keyBindings() {
aa := ui.KeyActions{
tcell.KeyEsc: ui.NewKeyAction("Back", b.app.PrevCmd, false),
tcell.KeyEnter: ui.NewKeyAction("Enter", b.enterCmd, false),
tcell.KeyCtrlD: ui.NewKeyAction("Delete", b.deleteCmd, false),
}

View File

@ -1,6 +1,7 @@
package view
import (
"context"
"strings"
"github.com/derailed/k9s/internal/resource"
@ -14,14 +15,17 @@ type Context struct {
// NewContext return a new context viewer.
func NewContext(title, gvr string, list resource.List) ResourceViewer {
c := Context{
return &Context{
Resource: NewResource(title, gvr, list),
}
}
func (c *Context) Init(ctx context.Context) {
c.extraActionsFn = c.extraActions
c.enterFn = c.useCtx
c.masterPage().SetSelectedFn(c.cleanser)
c.Resource.Init(ctx)
return &c
c.masterPage().SetSelectedFn(c.cleanser)
}
func (c *Context) extraActions(aa ui.KeyActions) {
@ -57,9 +61,7 @@ func (c *Context) useContext(name string) error {
return err
}
c.refresh()
if tv, ok := c.GetPrimitive("ctx").(*Table); ok {
tv.Select(1, 0)
}
c.masterPage().Select(1, 0)
return nil
}

View File

@ -0,0 +1,24 @@
package view
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCleaner(t *testing.T) {
uu := map[string]struct {
s, e string
}{
"normal": {"fred", "fred"},
"default": {"fred*", "fred"},
"delta": {"fred(𝜟)", "fred"},
}
v := Context{}
for k, u := range uu {
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, v.cleanser(u.s))
})
}
}

View File

@ -1,34 +1,17 @@
package view_test
// import (
// "testing"
import (
"testing"
// "github.com/derailed/k9s/internal/config"
// "github.com/derailed/k9s/internal/resource"
// "github.com/derailed/k9s/internal/view"
// "github.com/stretchr/testify/assert"
// )
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
)
// func TestContext(t *testing.T) {
// l := resource.NewContextList(nil, "fred")
// v := view.NewContext("blee", "", NewApp(config.NewConfig(ks{})), l).(*contextView)
func TestContext(t *testing.T) {
ctx := view.NewContext("ctx", "", resource.NewContextList(nil, "fred"))
ctx.Init(makeCtx())
// assert.Equal(t, 10, len(v.Hints()))
// }
// func TestCleaner(t *testing.T) {
// uu := map[string]struct {
// s, e string
// }{
// "normal": {"fred", "fred"},
// "default": {"fred*", "fred"},
// "delta": {"fred(𝜟)", "fred"},
// }
// v := contextView{}
// for k, u := range uu {
// t.Run(k, func(t *testing.T) {
// assert.Equal(t, u.e, v.cleanser(u.s))
// })
// }
// }
assert.Equal(t, "ctx", ctx.Name())
assert.Equal(t, 13, len(ctx.Hints()))
}

View File

@ -7,7 +7,6 @@ import (
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/ui"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
)
// MasterDetail presents a master-detail viewer.
@ -16,6 +15,7 @@ type MasterDetail struct {
enterFn enterFn
extraActionsFn func(ui.KeyActions)
master *Table
details *Details
currentNS string
title string
@ -32,7 +32,6 @@ func NewMasterDetail(title, ns string) *MasterDetail {
// Init initializes the viewer.
func (m *MasterDetail) Init(ctx context.Context) {
log.Debug().Msgf("\t>>>MasterDetail init %q", m.title)
app := ctx.Value(ui.KeyApp).(*App)
if m.currentNS != resource.NotNamespaced {
m.currentNS = app.Config.ActiveNamespace()
@ -40,15 +39,14 @@ func (m *MasterDetail) Init(ctx context.Context) {
m.PageStack.Init(ctx)
m.AddListener(app.Menu())
t := NewTable(m.title)
m.Push(t)
m.master = NewTable(m.title)
m.Push(m.master)
m.details = NewDetails(m.app, func(evt *tcell.EventKey) *tcell.EventKey {
m.Pop()
return nil
})
m.details.Init(ctx)
log.Debug().Msgf("\t<<<<MasterDetail INIT DONE!!")
}
// Hints returns the current viewer hints
@ -71,11 +69,11 @@ func (m *MasterDetail) setEnterFn(f enterFn) {
}
func (m *MasterDetail) showMaster() {
m.Show("table")
m.Show(m.master)
}
func (m *MasterDetail) masterPage() *Table {
return m.GetPrimitive(m.title).(*Table)
return m.master
}
func (m *MasterDetail) showDetails() {
@ -87,13 +85,7 @@ func (m *MasterDetail) detailsPage() *Details {
}
func (m *MasterDetail) isMaster() bool {
p := m.CurrentPage()
if p == nil {
return false
}
log.Debug().Msgf("!!!!!Checking MASTER %q vs %q -- %t", p.Name, m.title, p.Name == m.title)
return p.Name == m.title
return m.Current() == m.master
}
// ----------------------------------------------------------------------------
@ -109,6 +101,9 @@ func (m *MasterDetail) defaultActions(aa ui.KeyActions) {
}
func (m *MasterDetail) backCmd(evt *tcell.EventKey) *tcell.EventKey {
m.DumpPages()
m.DumpStack()
if !m.isMaster() {
return m.app.PrevCmd(evt)
}

View File

@ -1,27 +0,0 @@
package view
// import (
// "testing"
// "github.com/stretchr/testify/assert"
// )
// func TestNSCleanser(t *testing.T) {
// var v namespaceView
// uu := []struct {
// s, e string
// }{
// {"fred", "fred"},
// {"fred+", "fred"},
// {"fred(*)", "fred"},
// {"fred+(*)", "fred"},
// {"fred-blee+(*)", "fred-blee"},
// {"fred1-blee2+(*)", "fred1-blee2"},
// {"fred(𝜟)", "fred"},
// }
// for _, u := range uu {
// assert.Equal(t, u.e, v.cleanser(u.s))
// }
// }

View File

@ -1,6 +1,7 @@
package view
import (
"context"
"regexp"
"github.com/derailed/k9s/internal/config"
@ -24,15 +25,17 @@ type Namespace struct {
// NewNamespace returns a new viewer
func NewNamespace(title, gvr string, list resource.List) ResourceViewer {
n := Namespace{
return &Namespace{
Resource: NewResource(title, gvr, list),
}
}
func (n *Namespace) Init(ctx context.Context) {
n.extraActionsFn = n.extraActions
n.masterPage().SetSelectedFn(n.cleanser)
n.decorateFn = n.decorate
n.enterFn = n.switchNs
return &n
n.Resource.Init(ctx)
n.masterPage().SetSelectedFn(n.cleanser)
}
func (n *Namespace) extraActions(aa ui.KeyActions) {
@ -62,7 +65,7 @@ func (n *Namespace) useNamespace(ns string) {
if err := n.app.Config.Save(); err != nil {
log.Error().Err(err).Msg("Config file save failed!")
}
n.app.startInformer(ns)
n.app.switchNS(ns)
}
func (*Namespace) cleanser(s string) string {
@ -70,6 +73,10 @@ func (*Namespace) cleanser(s string) string {
}
func (n *Namespace) decorate(data resource.TableData) resource.TableData {
if n.app.Conn() == nil {
return resource.TableData{}
}
if _, ok := data.Rows[resource.AllNamespaces]; !ok {
if err := n.app.Conn().CheckNSAccess(""); err == nil {
data.Rows[resource.AllNamespace] = &resource.RowEvent{

View File

@ -0,0 +1,27 @@
package view
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNSCleanser(t *testing.T) {
var v Namespace
uu := []struct {
s, e string
}{
{"fred", "fred"},
{"fred+", "fred"},
{"fred(*)", "fred"},
{"fred+(*)", "fred"},
{"fred-blee+(*)", "fred-blee"},
{"fred1-blee2+(*)", "fred1-blee2"},
{"fred(𝜟)", "fred"},
}
for _, u := range uu {
assert.Equal(t, u.e, v.cleanser(u.s))
}
}

17
internal/view/ns_test.go Normal file
View File

@ -0,0 +1,17 @@
package view_test
import (
"testing"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
)
func TestNSCleanser(t *testing.T) {
ns := view.NewNamespace("ns", "", resource.NewNamespaceList(nil, ""))
ns.Init(makeCtx())
assert.Equal(t, "ns", ns.Name())
assert.Equal(t, 20, len(ns.Hints()))
}

View File

@ -47,6 +47,7 @@ func NewPolicy(app *App, subject, name string) *Policy {
// Init the view.
func (p *Policy) Init(ctx context.Context) {
p.Table.Init(ctx)
p.bindKeys()
p.SetSortCol(1, len(rbacHeader), false)
p.Start()

View File

@ -64,7 +64,6 @@ func (r *Resource) Init(ctx context.Context) {
table.Select(1, 0)
}
}
log.Debug().Msgf("<<<< RESOURCE INIT")
}
// Start initializes updates.

View File

@ -65,7 +65,7 @@ func (s *ScreenDump) Start() {
var ctx context.Context
ctx, s.cancelFn = context.WithCancel(context.Background())
if err := s.watchDumpDir(ctx); err != nil {
s.app.Flash().Errf("Unable to watch dumpmarks directory %s", err)
s.app.Flash().Errf("Unable to watch screen dumps directory %s", err)
}
}

View File

@ -9,12 +9,14 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// StatefulSet represents a statefulset viewer.
type StatefulSet struct {
*LogResource
scalableResource *ScalableResource
restartableResource *RestartableResource
}
// NewStatefulSet returns a new viewer.
func NewStatefulSet(title, gvr string, list resource.List) ResourceViewer {
l := NewLogResource(title, gvr, list)
s := StatefulSet{

17
internal/view/sts_test.go Normal file
View File

@ -0,0 +1,17 @@
package view_test
import (
"testing"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
)
func TestStatefulSetNew(t *testing.T) {
s := view.NewStatefulSet("sts", "", resource.NewStatefulSetList(nil, ""))
s.Init(makeCtx())
assert.Equal(t, "sts", s.Name())
assert.Equal(t, 24, len(s.Hints()))
}

View File

@ -35,25 +35,23 @@ type (
// NewSubject returns a new subject viewer.
func NewSubject(title, gvr string, list resource.List) ResourceViewer {
s := Subject{}
s.Table = NewTable("Subject")
s.SetActiveNS("*")
s.SetColorerFn(rbacColorer)
s.bindKeys()
return &s
return &Subject{
Table: NewTable("Subject"),
}
}
// Init initializes the view.
func (s *Subject) Init(ctx context.Context) {
s.SetActiveNS("*")
s.SetColorerFn(rbacColorer)
s.Table.Init(ctx)
s.bindKeys()
s.SetSortCol(1, len(rbacHeader), true)
s.subjectKind = mapCmdSubject(s.app.Config.K9s.ActiveCluster().View.Active)
s.SetBaseTitle(s.subjectKind)
s.Start()
s.refresh()
s.SelectRow(1, true)
s.refresh()
}
func (s *Subject) Start() {
@ -125,6 +123,7 @@ func (s *Subject) refresh() {
}
func (s *Subject) policyCmd(evt *tcell.EventKey) *tcell.EventKey {
log.Debug().Msg("YO!!")
if !s.RowSelected() {
return evt
}
@ -163,6 +162,9 @@ func (s *Subject) backCmd(evt *tcell.EventKey) *tcell.EventKey {
func (s *Subject) reconcile() (resource.TableData, error) {
var table resource.TableData
if s.app.Conn() == nil {
return table, nil
}
evts, err := s.clusterSubjects()
if err != nil {

View File

@ -0,0 +1,16 @@
package view_test
import (
"testing"
"github.com/derailed/k9s/internal/view"
"github.com/stretchr/testify/assert"
)
func TestSubjectNew(t *testing.T) {
s := view.NewSubject("subject", "", nil)
s.Init(makeCtx())
assert.Equal(t, "subject", s.Name())
assert.Equal(t, 11, len(s.Hints()))
}

View File

@ -22,8 +22,6 @@ func NewTable(title string) *Table {
}
func (t *Table) Init(ctx context.Context) {
log.Debug().Msgf("VIEW Table INIT %q", t.GetBaseTitle())
t.app = ctx.Value(ui.KeyApp).(*App)
ctx = context.WithValue(ctx, ui.KeyStyles, t.app.Styles)

View File

@ -54,6 +54,7 @@ type StoreInformer interface {
// Informer represents a collection of cluster wide watchers.
type Informer struct {
Namespace string
informers map[string]StoreInformer
client k8s.Connection
podInformer *Pod
@ -65,6 +66,7 @@ type Informer struct {
func NewInformer(client k8s.Connection, ns string) (*Informer, error) {
i := Informer{
client: client,
Namespace: ns,
informers: map[string]StoreInformer{},
}
if err := client.CheckNSAccess(ns); err != nil {
@ -76,6 +78,13 @@ func NewInformer(client k8s.Connection, ns string) (*Informer, error) {
return &i, nil
}
func (i *Informer) Dump() {
log.Debug().Msgf("Informer Dump")
for k := range i.informers {
log.Debug().Msgf("\t%s", k)
}
}
func (i *Informer) init(ns string) {
log.Debug().Msgf(">>>>> Starting Informer in namespace %q", ns)

View File

@ -11,7 +11,7 @@ import (
const (
// NodeIndex marker for stored nodes.
NodeIndex string = "no"
NodeIndex string = "nodes"
nodeCols = 12
)

View File

@ -13,7 +13,7 @@ import (
const (
// PodIndex marker for stored pods.
PodIndex string = "po"
PodIndex string = "pods"
podCols = 11
)