From 98b07e69614303074329660383725cff055e3080 Mon Sep 17 00:00:00 2001 From: derailed Date: Thu, 20 Feb 2020 00:03:32 -0700 Subject: [PATCH] checkpoint --- .gitignore | 1 + internal/config/styles.go | 6 +- internal/model/pulse.go | 10 +-- .../model/{health_one.go => pulse_health.go} | 18 ++--- internal/tchart/component.go | 2 +- internal/tchart/dot_matrix_test.go | 2 +- internal/tchart/gauge.go | 5 +- internal/tchart/sparkline.go | 71 +++++++++---------- internal/ui/flash.go | 2 +- internal/ui/logo.go | 2 +- internal/view/actions.go | 6 +- internal/view/alias.go | 2 +- internal/view/app.go | 14 ++-- internal/view/cluster_info.go | 1 + internal/view/ns.go | 2 +- internal/view/pf_dialog.go | 16 +++-- internal/view/pf_extender.go | 3 +- internal/view/pulse.go | 16 +++-- internal/view/registrar.go | 2 +- 19 files changed, 92 insertions(+), 89 deletions(-) rename internal/model/{health_one.go => pulse_health.go} (81%) diff --git a/.gitignore b/.gitignore index cd5193e4..81b09721 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ gen.sh *.log *~ faas +demos \ No newline at end of file diff --git a/internal/config/styles.go b/internal/config/styles.go index b45c17a0..e14ecdf4 100644 --- a/internal/config/styles.go +++ b/internal/config/styles.go @@ -210,9 +210,9 @@ func newStyle() Style { func newCharts() Charts { return Charts{ - BgColor: "#111111", - DialBgColor: "#111111", - ChartBgColor: "#111111", + BgColor: "default", + DialBgColor: "default", + ChartBgColor: "default", DefaultDialColors: Colors{Color("palegreen"), Color("orangered")}, DefaultChartColors: Colors{Color("palegreen"), Color("orangered")}, } diff --git a/internal/model/pulse.go b/internal/model/pulse.go index c19c79c4..9db88a0b 100644 --- a/internal/model/pulse.go +++ b/internal/model/pulse.go @@ -13,6 +13,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +const defaultRefreshRate = 5 * time.Second + // PulseListener represents a health model listener. type PulseListener interface { // PulseChanged notifies the model data changed. @@ -29,14 +31,14 @@ type Pulse struct { inUpdate int32 listeners []PulseListener refreshRate time.Duration - health *Health + health *PulseHealth data health.Checks } func NewPulse(gvr string) *Pulse { return &Pulse{ gvr: gvr, - refreshRate: 2 * time.Second, + refreshRate: defaultRefreshRate, } } @@ -48,7 +50,7 @@ func (p *Pulse) Watch(ctx context.Context) { func (p *Pulse) updater(ctx context.Context) { defer log.Debug().Msgf("Pulse canceled -- %q", p.gvr) - rate := initTreeRefreshRate + rate := initRefreshRate for { select { case <-ctx.Done(): @@ -88,7 +90,7 @@ func (p *Pulse) list(ctx context.Context) ([]runtime.Object, error) { return nil, fmt.Errorf("expected Factory in context but got %T", ctx.Value(internal.KeyFactory)) } if p.health == nil { - p.health = NewHealth(f) + p.health = NewPulseHealth(f) } ctx = context.WithValue(ctx, internal.KeyFields, "") ctx = context.WithValue(ctx, internal.KeyWithMetrics, false) diff --git a/internal/model/health_one.go b/internal/model/pulse_health.go similarity index 81% rename from internal/model/health_one.go rename to internal/model/pulse_health.go index 99372c36..12de902d 100644 --- a/internal/model/health_one.go +++ b/internal/model/pulse_health.go @@ -14,19 +14,19 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -type Health struct { +type PulseHealth struct { factory dao.Factory } -func NewHealth(f dao.Factory) *Health { - return &Health{ +func NewPulseHealth(f dao.Factory) *PulseHealth { + return &PulseHealth{ factory: f, } } -func (h *Health) List(ctx context.Context, ns string) ([]runtime.Object, error) { +func (h *PulseHealth) List(ctx context.Context, ns string) ([]runtime.Object, error) { defer func(t time.Time) { - log.Debug().Msgf("HealthCheck %v", time.Since(t)) + log.Debug().Msgf("PulseHealthCheck %v", time.Since(t)) }(time.Now()) gvrs := []string{ @@ -60,7 +60,7 @@ func (h *Health) List(ctx context.Context, ns string) ([]runtime.Object, error) return hh, nil } -func (h *Health) checkMetrics() (health.Checks, error) { +func (h *PulseHealth) checkMetrics() (health.Checks, error) { dial := client.DialMetrics(h.factory.Client()) nmx, err := dial.FetchNodesMetrics() if err != nil { @@ -81,11 +81,7 @@ func (h *Health) checkMetrics() (health.Checks, error) { return health.Checks{c1, c2}, nil } -func (h *Health) check(ctx context.Context, ns, gvr string) (*health.Check, error) { - defer func(t time.Time) { - log.Debug().Msgf(" CHECK %s - %v", gvr, time.Since(t)) - }(time.Now()) - +func (h *PulseHealth) check(ctx context.Context, ns, gvr string) (*health.Check, error) { meta, ok := Registry[gvr] if !ok { return nil, fmt.Errorf("No meta for %q", gvr) diff --git a/internal/tchart/component.go b/internal/tchart/component.go index 64e45757..42733082 100644 --- a/internal/tchart/component.go +++ b/internal/tchart/component.go @@ -31,7 +31,7 @@ func NewComponent(id string) *Component { return &Component{ Box: tview.NewBox(), id: id, - noColor: tcell.ColorBlack, + noColor: tcell.ColorDefault, seriesColors: []tcell.Color{tview.Styles.PrimaryTextColor, tview.Styles.FocusColor}, dimmed: tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor).Foreground(tcell.ColorGray).Dim(true), } diff --git a/internal/tchart/dot_matrix_test.go b/internal/tchart/dot_matrix_test.go index 8f032e7d..e3e8e122 100644 --- a/internal/tchart/dot_matrix_test.go +++ b/internal/tchart/dot_matrix_test.go @@ -50,7 +50,7 @@ func TestDial(t *testing.T) { // Helpers... -const hChar, vChar = '⠶', '⠿' +const hChar, vChar = '▤', '▥' var numbers = []tchart.Matrix{ [][]rune{ diff --git a/internal/tchart/gauge.go b/internal/tchart/gauge.go index 87fa9ce4..b40fa546 100644 --- a/internal/tchart/gauge.go +++ b/internal/tchart/gauge.go @@ -56,6 +56,9 @@ func (g *Gauge) drawNum(sc tcell.Screen, ok bool, o image.Point, n int, dn delta } dm, sig := NewDotMatrix(5, 3), n == 0 + if n == 0 { + style = g.dimmed + } for i := 0; i < len(ns); i++ { if ns[i] == '0' && !sig { g.drawDial(sc, dm.Print(int(ns[i]-48)), o, g.dimmed) @@ -102,7 +105,7 @@ func (g *Gauge) Draw(sc tcell.Screen) { if g.HasFocus() { legend = "[:aqua]" + g.legend + "[::]" } - tview.Print(sc, legend, rect.Min.X, rect.Max.Y-1, rect.Dx(), tview.AlignCenter, tcell.ColorWhite) + tview.Print(sc, legend, rect.Min.X, rect.Max.Y, rect.Dx(), tview.AlignCenter, tcell.ColorWhite) } } diff --git a/internal/tchart/sparkline.go b/internal/tchart/sparkline.go index e4031892..690578a0 100644 --- a/internal/tchart/sparkline.go +++ b/internal/tchart/sparkline.go @@ -40,8 +40,7 @@ func (m Metric) Sum() int { type SparkLine struct { *Component - data []Metric - lastWidth int + data []Metric } // NewSparkLine returns a new graph. @@ -75,24 +74,24 @@ func (s *SparkLine) Draw(screen tcell.Screen) { } rect := s.asRect() - s.lastWidth = rect.Dx() s.cutSet(rect.Dx()) max := s.computeMax() - cX := rect.Min.X + 1 - if len(s.data) < rect.Dx() { - cX = rect.Max.X - len(s.data) + cX, idx := rect.Min.X+1, 0 + if len(s.data)*2 < rect.Dx() { + cX = rect.Max.X - len(s.data)*2 + } else { + idx = len(s.data) - rect.Dx()/2 } scale := float64(len(sparks)) * float64((rect.Dy() - pad)) / float64(max) - c1, c2 := s.colorForSeries() - for _, d := range s.data { + for _, d := range s.data[idx:] { b := toBlocks(d, scale) cY := rect.Max.Y - pad - cY = s.drawBlock(screen, cX, cY, b.oks, c1) + s.drawBlock(screen, cX, cY, b.oks, c1) s.drawBlock(screen, cX, cY, b.errs, c2) - cX++ + cX += 2 } if rect.Dx() > 0 && rect.Dy() > 0 && s.legend != "" { @@ -100,11 +99,11 @@ func (s *SparkLine) Draw(screen tcell.Screen) { if s.HasFocus() { legend = "[:aqua:]" + s.legend + "[::]" } - tview.Print(screen, legend, rect.Min.X, rect.Max.Y-1, rect.Dx(), tview.AlignCenter, tcell.ColorWhite) + tview.Print(screen, legend, rect.Min.X, rect.Max.Y, rect.Dx(), tview.AlignCenter, tcell.ColorWhite) } } -func (s *SparkLine) drawBlock(screen tcell.Screen, x, y int, b block, c tcell.Color) int { +func (s *SparkLine) drawBlock(screen tcell.Screen, x, y int, b block, c tcell.Color) { style := tcell.StyleDefault.Foreground(c).Background(s.bgColor) for i := 0; i < b.full; i++ { @@ -114,48 +113,46 @@ func (s *SparkLine) drawBlock(screen tcell.Screen, x, y int, b block, c tcell.Co if b.partial != 0 { screen.SetContent(x, y, b.partial, nil, style) } - - return y } -func (s *SparkLine) cutSet(w int) { - if w <= 0 || len(s.data) == 0 { +func (s *SparkLine) cutSet(width int) { + if width <= 0 || len(s.data) == 0 { return } - if w < len(s.data) { - s.data = s.data[len(s.data)-w:] + if len(s.data) >= width*2 { + s.data = s.data[len(s.data)-width:] } } func (s *SparkLine) computeMax() int { var max int for _, d := range s.data { - sum := d.Sum() - if sum > max { - max = sum + if max < d.OK { + max = d.OK } } return max } -func toBlocks(value Metric, scale float64) blocks { - if value.Sum() <= 0 { +func toBlocks(m Metric, scale float64) blocks { + if m.Sum() <= 0 { return blocks{} } - - oks := int(math.Floor(float64(value.OK) * scale)) - part, okB := oks%len(sparks), block{full: oks / len(sparks)} - if part > 0 { - okB.partial = sparks[part-1] - } - - errs := int(math.Round(float64(value.Fault) * scale)) - part, errB := errs%len(sparks), block{full: errs / len(sparks)} - if part > 0 { - errB.partial = sparks[part-1] - } - - return blocks{oks: okB, errs: errB} + return blocks{oks: makeBlocks(m.OK, false, scale), errs: makeBlocks(m.Fault, true, scale)} +} + +func makeBlocks(v int, isErr bool, scale float64) block { + scaled := int(math.Round(float64(v) * scale)) + part, b := scaled%len(sparks), block{full: scaled / len(sparks)} + // Err might get scaled way down if so nudge. + if v > 0 && isErr && scaled == 0 { + part = 1 + } + if part > 0 { + b.partial = sparks[part-1] + } + + return b } diff --git a/internal/ui/flash.go b/internal/ui/flash.go index acd6d588..5faa123d 100644 --- a/internal/ui/flash.go +++ b/internal/ui/flash.go @@ -31,7 +31,7 @@ func NewFlash(app *App) *Flash { TextView: tview.NewTextView(), } f.SetTextColor(tcell.ColorAqua) - f.SetTextAlign(tview.AlignLeft) + f.SetTextAlign(tview.AlignCenter) f.SetBorderPadding(0, 0, 1, 1) f.app.Styles.AddListener(&f) diff --git a/internal/ui/logo.go b/internal/ui/logo.go index 500b8840..3bc0a83b 100644 --- a/internal/ui/logo.go +++ b/internal/ui/logo.go @@ -25,7 +25,7 @@ func NewLogo(styles *config.Styles) *Logo { } l.SetDirection(tview.FlexRow) l.AddItem(l.logo, 0, 6, false) - l.AddItem(l.status, 0, 1, false) + l.AddItem(l.status, 1, 0, false) l.refreshLogo(styles.Body().LogoColor) l.SetBackgroundColor(styles.BgColor()) styles.AddListener(&l) diff --git a/internal/view/actions.go b/internal/view/actions.go index f0ec5529..9bd173b1 100644 --- a/internal/view/actions.go +++ b/internal/view/actions.go @@ -68,14 +68,14 @@ func hotKeyActions(r Runner, aa ui.KeyActions) { } aa[key] = ui.NewSharedKeyAction( hk.Description, - gotoCmd(r, hk.Command), + gotoCmd(r, "", hk.Command), false) } } -func gotoCmd(r Runner, cmd string) ui.ActionHandler { +func gotoCmd(r Runner, cmd, path string) ui.ActionHandler { return func(evt *tcell.EventKey) *tcell.EventKey { - if err := r.App().gotoResource(cmd, true); err != nil { + if err := r.App().gotoResource(cmd, path, true); err != nil { r.App().Flash().Err(err) } return nil diff --git a/internal/view/alias.go b/internal/view/alias.go index 4e822f2f..430c9635 100644 --- a/internal/view/alias.go +++ b/internal/view/alias.go @@ -59,7 +59,7 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { if r != 0 { s := ui.TrimCell(a.GetTable().SelectTable, r, 1) tokens := strings.Split(s, ",") - if err := a.App().gotoResource(tokens[0], true); err != nil { + if err := a.App().gotoResource(tokens[0], "", true); err != nil { a.App().Flash().Err(err) } return nil diff --git a/internal/view/app.go b/internal/view/app.go index 98997efe..8a68ffb0 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -104,8 +104,8 @@ func (a *App) Init(version string, rate int) error { main := tview.NewFlex().SetDirection(tview.FlexRow) main.AddItem(a.statusIndicator(), 1, 1, false) main.AddItem(a.Content, 0, 10, true) - main.AddItem(a.Crumbs(), 2, 1, false) - main.AddItem(flash, 2, 1, false) + main.AddItem(a.Crumbs(), 1, 1, false) + main.AddItem(flash, 1, 1, false) a.Main.AddPage("main", main, true, false) a.Main.AddPage("splash", ui.NewSplash(a.Styles, version), true, true) @@ -136,7 +136,7 @@ func (a *App) toggleHeader(flag bool) { } if a.showHeader { flex.RemoveItemAtIndex(0) - flex.AddItemAtIndex(0, a.buildHeader(), 7, 1, false) + flex.AddItemAtIndex(0, a.buildHeader(), 8, 1, false) } else { flex.RemoveItemAtIndex(0) flex.AddItemAtIndex(0, a.statusIndicator(), 1, 1, false) @@ -271,7 +271,7 @@ func (a *App) switchCtx(name string, loadPods bool) error { } a.Flash().Infof("Switching context to %s", name) a.ReloadStyles(name) - if err := a.gotoResource("pods", true); loadPods && err != nil { + if err := a.gotoResource("pods", "", true); loadPods && err != nil { a.Flash().Err(err) } a.clusterModel.Reset(a.factory) @@ -382,7 +382,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() { - if err := a.gotoResource(a.GetCmd(), true); err != nil { + if err := a.gotoResource(a.GetCmd(), "", true); err != nil { log.Error().Err(err).Msgf("Goto resource for %q failed", a.GetCmd()) a.Flash().Err(err) } @@ -431,8 +431,8 @@ func (a *App) viewResource(gvr, path string, clearStack bool) error { return a.command.run(gvr, path, clearStack) } -func (a *App) gotoResource(cmd string, clearStack bool) error { - return a.command.run(cmd, "", clearStack) +func (a *App) gotoResource(cmd, path string, clearStack bool) error { + return a.command.run(cmd, path, clearStack) } func (a *App) inject(c model.Component) error { diff --git a/internal/view/cluster_info.go b/internal/view/cluster_info.go index df75851e..b315441e 100644 --- a/internal/view/cluster_info.go +++ b/internal/view/cluster_info.go @@ -30,6 +30,7 @@ func NewClusterInfo(app *App) *ClusterInfo { // Init initializes the view. func (c *ClusterInfo) Init() { + c.SetBorderPadding(0, 0, 1, 0) c.app.Styles.AddListener(c) c.layout() c.StylesChanged(c.app.Styles) diff --git a/internal/view/ns.go b/internal/view/ns.go index 1ad2a585..1a79fb94 100644 --- a/internal/view/ns.go +++ b/internal/view/ns.go @@ -42,7 +42,7 @@ func (n *Namespace) bindKeys(aa ui.KeyActions) { func (n *Namespace) switchNs(app *App, model ui.Tabular, gvr, path string) { n.useNamespace(path) - if err := app.gotoResource("pods", true); err != nil { + if err := app.gotoResource("pods", "", true); err != nil { app.Flash().Err(err) } } diff --git a/internal/view/pf_dialog.go b/internal/view/pf_dialog.go index a7b0e4b8..973644df 100644 --- a/internal/view/pf_dialog.go +++ b/internal/view/pf_dialog.go @@ -27,13 +27,13 @@ func ShowPortForwards(v ResourceViewer, path string, ports []string, okFn PortFo SetFieldTextColor(styles.K9s.Info.SectionColor.Color()) p1, p2, address := ports[0], extractPort(ports[0]), "localhost" - f.AddInputField("Container Port:", p1, 20, nil, func(p string) { + f.AddInputField("Container Port:", p1, 30, nil, func(p string) { p1 = p }) - f.AddInputField("Local Port:", p2, 20, nil, func(p string) { + f.AddInputField("Local Port:", p2, 30, nil, func(p string) { p2 = p }) - f.AddInputField("Address:", address, 20, nil, func(h string) { + f.AddInputField("Address:", address, 30, nil, func(h string) { address = h }) @@ -48,22 +48,24 @@ func ShowPortForwards(v ResourceViewer, path string, ports []string, okFn PortFo okFn(v, path, extractContainer(p1), tunnel) }) f.AddButton("Cancel", func() { - DismissPortForwards(pages) + DismissPortForwards(v.App(), pages) }) modal := tview.NewModalForm(fmt.Sprintf("", path), f) modal.SetText("Exposed Ports: " + strings.Join(ports, ",")) modal.SetDoneFunc(func(_ int, b string) { - DismissPortForwards(pages) + DismissPortForwards(v.App(), pages) }) - pages.AddPage(portForwardKey, modal, false, false) + pages.AddPage(portForwardKey, modal, false, true) pages.ShowPage(portForwardKey) + v.App().SetFocus(pages.GetPrimitive(portForwardKey)) } // DismissPortForwards dismiss the port forward dialog. -func DismissPortForwards(p *ui.Pages) { +func DismissPortForwards(app *App, p *ui.Pages) { p.RemovePage(portForwardKey) + app.SetFocus(p.CurrentPage().Item) } // ---------------------------------------------------------------------------- diff --git a/internal/view/pf_extender.go b/internal/view/pf_extender.go index 1393d37a..2907f2a8 100644 --- a/internal/view/pf_extender.go +++ b/internal/view/pf_extender.go @@ -76,7 +76,7 @@ func runForward(v ResourceViewer, pf watch.Forwarder, f *portforward.PortForward v.App().QueueUpdateDraw(func() { v.App().Flash().Infof("PortForward activated %s:%s", pf.Path(), pf.Ports()[0]) - DismissPortForwards(v.App().Content.Pages) + DismissPortForwards(v.App(), v.App().Content.Pages) }) pf.SetActive(true) @@ -124,7 +124,6 @@ func showFwdDialog(v ResourceViewer, path string, cb PortForwardFunc) error { ports = append(ports, client.FQN(co, p.Name)+":"+strconv.Itoa(int(p.ContainerPort))) } } - if len(ports) == 0 { return fmt.Errorf("no tcp ports found on %s", path) } diff --git a/internal/view/pulse.go b/internal/view/pulse.go index fcebef6a..21db7bce 100644 --- a/internal/view/pulse.go +++ b/internal/view/pulse.go @@ -141,15 +141,19 @@ func (p *Pulse) PulseChanged(c *health.Check) { v.SetLegend(fmt.Sprintf(" %s - %dMi", strings.Title(gvr.R()), c.Tally(health.OK))) default: nn := v.GetSeriesColorNames() - v.SetLegend(fmt.Sprintf(" %s(%d:[%s::]%d:[%s::b]%d[-::])", + if c.Tally(health.OK) == 0 { + nn[0] = "gray" + } + if c.Tally(health.Toast) == 0 { + nn[1] = "gray" + } + v.SetLegend(fmt.Sprintf(" %s - [%s::]%d/[%s::b]%d[-::]", strings.Title(gvr.R()), - c.Tally(health.Corpus), nn[0], c.Tally(health.OK), nn[1], c.Tally(health.Toast), - ), - ) + )) } v.Add(tchart.Metric{OK: c.Tally(health.OK), Fault: c.Tally(health.Toast)}) } @@ -173,12 +177,10 @@ func (p *Pulse) bindKeys() { } func (p *Pulse) keyboard(evt *tcell.EventKey) *tcell.EventKey { - log.Debug().Msgf("Pulse GOT EVENT %#v", evt) key := evt.Key() if key == tcell.KeyRune { key = tcell.Key(evt.Rune()) } - if a, ok := p.actions[key]; ok { return a.Action(evt) } @@ -275,7 +277,7 @@ func (p *Pulse) enterCmd(evt *tcell.EventKey) *tcell.EventKey { } log.Debug().Msgf("Selected %s", s.ID()) gvr := client.NewGVR(s.ID()) - if err := p.App().gotoResource(gvr.R(), false); err != nil { + if err := p.App().gotoResource(gvr.R(), "", false); err != nil { p.App().Flash().Err(err) } diff --git a/internal/view/registrar.go b/internal/view/registrar.go index 417f79e8..9adf1eb4 100644 --- a/internal/view/registrar.go +++ b/internal/view/registrar.go @@ -137,7 +137,7 @@ func extViewers(vv MetaViewers) { func showCRD(app *App, _ ui.Tabular, _, path string) { _, crdGVR := client.Namespaced(path) tokens := strings.Split(crdGVR, ".") - if err := app.gotoResource(tokens[0], false); err != nil { + if err := app.gotoResource(tokens[0], "", false); err != nil { app.Flash().Err(err) } }