diff --git a/go.mod b/go.mod index 7ee8986a..af5486d6 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/derailed/k9s go 1.13 +replace github.com/derailed/tview => /Users/fernand/go_wk/derailed/src/github.com/derailed/tview + replace ( github.com/docker/docker => github.com/docker/docker v1.4.2-0.20181221150755-2cb26cfe9cbf k8s.io/api => k8s.io/api v0.0.0-20190918155943-95b840bb6a1f diff --git a/internal/render/helpers.go b/internal/render/helpers.go index e5108165..8e81cde3 100644 --- a/internal/render/helpers.go +++ b/internal/render/helpers.go @@ -112,6 +112,11 @@ func join(a []string, sep string) string { return buff.String() } +// ToPerc prints a number as percentage. +func ToPerc(f float64) string { + return AsPerc(f) + "%" +} + // AsPerc prints a number as a percentage. func AsPerc(f float64) string { return strconv.Itoa(int(f)) diff --git a/internal/tchart/component.go b/internal/tchart/component.go index 42733082..5762d765 100644 --- a/internal/tchart/component.go +++ b/internal/tchart/component.go @@ -6,7 +6,6 @@ import ( "github.com/derailed/tview" "github.com/gdamore/tcell" - "github.com/rs/zerolog/log" ) const ( @@ -61,9 +60,7 @@ func (c *Component) InputHandler() func(event *tcell.EventKey, setFocus func(p t return c.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { switch key := event.Key(); key { case tcell.KeyEnter: - log.Debug().Msgf("YO %s ENTER!!", c.id) case tcell.KeyBacktab, tcell.KeyTab: - log.Debug().Msgf("YO %s TAB!!", c.id) if c.blur != nil { c.blur(key) } @@ -113,7 +110,8 @@ func (c *Component) GetSeriesColorNames() []string { func (c *Component) colorForSeries() (tcell.Color, tcell.Color) { c.mx.RLock() defer c.mx.RUnlock() - if len(c.seriesColors) > 1 { + + if len(c.seriesColors) == 2 { return c.seriesColors[0], c.seriesColors[1] } return okColor, faultColor diff --git a/internal/tchart/component_int_test.go b/internal/tchart/component_int_test.go new file mode 100644 index 00000000..ec01a381 --- /dev/null +++ b/internal/tchart/component_int_test.go @@ -0,0 +1,25 @@ +package tchart + +import ( + "image" + "testing" + + "github.com/derailed/tview" + "github.com/stretchr/testify/assert" +) + +func TestComponentAsRect(t *testing.T) { + c := NewComponent("fred") + r := image.Rectangle{Min: image.Point{X: 0, Y: 0}, Max: image.Point{X: 15, Y: 10}} + + assert.Equal(t, r, c.asRect()) +} + +func TestComponentColorForSeries(t *testing.T) { + c := NewComponent("fred") + okC, errC := c.colorForSeries() + + assert.Equal(t, tview.Styles.PrimaryTextColor, okC) + assert.Equal(t, tview.Styles.FocusColor, errC) + assert.Equal(t, []string{"white", "green"}, c.GetSeriesColorNames()) +} diff --git a/internal/tchart/component_test.go b/internal/tchart/component_test.go new file mode 100644 index 00000000..88156f5c --- /dev/null +++ b/internal/tchart/component_test.go @@ -0,0 +1,25 @@ +package tchart_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/tchart" + "github.com/gdamore/tcell" + "github.com/stretchr/testify/assert" +) + +func TestCoSeriesColorNames(t *testing.T) { + c := tchart.NewComponent("fred") + + c.SetSeriesColors(tcell.ColorGreen, tcell.ColorBlue) + + assert.Equal(t, []string{"green", "blue"}, c.GetSeriesColorNames()) +} + +func TestComponentAsRect(t *testing.T) { + c := tchart.NewComponent("fred") + + c.SetSeriesColors(tcell.ColorGreen, tcell.ColorBlue) + + assert.Equal(t, []string{"green", "blue"}, c.GetSeriesColorNames()) +} diff --git a/internal/tchart/dot_matrix_test.go b/internal/tchart/dot_matrix_test.go index e3e8e122..4fb1344b 100644 --- a/internal/tchart/dot_matrix_test.go +++ b/internal/tchart/dot_matrix_test.go @@ -38,12 +38,12 @@ func TestSegmentFor(t *testing.T) { } } -func TestDial(t *testing.T) { +func TestDial5x3(t *testing.T) { d := tchart.NewDotMatrix(5, 3) for n := 0; n <= 9; n++ { i := n t.Run(strconv.Itoa(n), func(t *testing.T) { - assert.Equal(t, numbers[i], d.Print(i)) + assert.Equal(t, numbers3x5[i], d.Print(i)) }) } } @@ -52,7 +52,7 @@ func TestDial(t *testing.T) { const hChar, vChar = '▤', '▥' -var numbers = []tchart.Matrix{ +var numbers3x5 = []tchart.Matrix{ [][]rune{ {hChar, hChar, hChar}, {vChar, ' ', vChar}, diff --git a/internal/tchart/gauge.go b/internal/tchart/gauge.go index f019b4d2..c1e635de 100644 --- a/internal/tchart/gauge.go +++ b/internal/tchart/gauge.go @@ -52,33 +52,6 @@ func (g *Gauge) Add(m Metric) { g.data = m } -func (g *Gauge) drawNum(sc tcell.Screen, ok bool, o image.Point, n int, dn delta, ns string, style tcell.Style) { - c1, _ := g.colorForSeries() - if ok { - o.X -= 1 - style = style.Foreground(c1) - printDelta(sc, dn, o, style) - o.X += 1 - } - - 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) - } else { - sig = true - g.drawDial(sc, dm.Print(int(ns[i]-48)), o, style) - } - o.X += 5 - } - if !ok { - printDelta(sc, dn, o, style) - } -} - // Draw draws the primitive. func (g *Gauge) Draw(sc tcell.Screen) { g.Component.Draw(sc) @@ -87,7 +60,7 @@ func (g *Gauge) Draw(sc tcell.Screen) { defer g.mx.RUnlock() rect := g.asRect() - mid := image.Point{X: rect.Min.X + rect.Dx()/2 - 2, Y: rect.Min.Y + rect.Dy()/2 - 2} + mid := image.Point{X: rect.Min.X + rect.Dx()/2 - 1, Y: rect.Min.Y + rect.Dy()/2 - 2} style := tcell.StyleDefault.Background(g.bgColor) style = style.Foreground(tcell.ColorYellow) @@ -129,6 +102,33 @@ func (g *Gauge) drawDial(sc tcell.Screen, m Matrix, o image.Point, style tcell.S } } +func (g *Gauge) drawNum(sc tcell.Screen, ok bool, o image.Point, n int, dn delta, ns string, style tcell.Style) { + c1, _ := g.colorForSeries() + if ok { + o.X -= 1 + style = style.Foreground(c1) + printDelta(sc, dn, o, style) + o.X += 1 + } + + 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) + } else { + sig = true + g.drawDial(sc, dm.Print(int(ns[i]-48)), o, style) + } + o.X += 5 + } + if !ok { + printDelta(sc, dn, o, style) + } +} + // ---------------------------------------------------------------------------- // Helpers... diff --git a/internal/tchart/gauge_int_test.go b/internal/tchart/gauge_int_test.go new file mode 100644 index 00000000..f34876b8 --- /dev/null +++ b/internal/tchart/gauge_int_test.go @@ -0,0 +1,35 @@ +package tchart + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestComputeDeltas(t *testing.T) { + uu := map[string]struct { + d1, d2 int + e delta + }{ + "same": { + e: DeltaSame, + }, + "more": { + d1: 10, + d2: 20, + e: DeltaMore, + }, + "less": { + d1: 20, + d2: 10, + e: DeltaLess, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, computeDelta(u.d1, u.d2)) + }) + } +} diff --git a/internal/tchart/gauge_test.go b/internal/tchart/gauge_test.go new file mode 100644 index 00000000..dcfef73d --- /dev/null +++ b/internal/tchart/gauge_test.go @@ -0,0 +1,88 @@ +package tchart_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/tchart" + "github.com/stretchr/testify/assert" +) + +func TestMetricsMaxDigits(t *testing.T) { + uu := map[string]struct { + m tchart.Metric + e int + }{ + "empty": { + e: 1, + }, + "oks": { + m: tchart.Metric{OK: 100, Fault: 10}, + e: 3, + }, + "errs": { + m: tchart.Metric{OK: 10, Fault: 1000}, + e: 4, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, u.m.MaxDigits()) + }) + } +} + +func TestMetricsMax(t *testing.T) { + uu := map[string]struct { + m tchart.Metric + e int + }{ + "empty": { + e: 0, + }, + "max_ok": { + m: tchart.Metric{OK: 100, Fault: 10}, + e: 100, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, u.m.Max()) + }) + } +} + +func TestGauge(t *testing.T) { + uu := map[string]struct { + mm []tchart.Metric + e int + }{ + "empty": { + e: 1, + }, + "oks": { + mm: []tchart.Metric{{OK: 100, Fault: 10}}, + e: 3, + }, + "errs": { + mm: []tchart.Metric{{OK: 10, Fault: 1000}}, + e: 4, + }, + } + + for k := range uu { + u := uu[k] + g := tchart.NewGauge("fred") + assert.True(t, g.IsDial()) + for _, m := range u.mm { + g.Add(m) + } + t.Run(k, func(t *testing.T) { + // assert.Equal(t, u.e, u.m.MaxDigits()) + }) + } + +} diff --git a/internal/tchart/sparkline.go b/internal/tchart/sparkline.go index 5b157bc1..b3419fb2 100644 --- a/internal/tchart/sparkline.go +++ b/internal/tchart/sparkline.go @@ -2,6 +2,7 @@ package tchart import ( "fmt" + "image" "math" "github.com/derailed/tview" @@ -24,13 +25,18 @@ type Metric struct { OK, Fault int } -// MaxDigits returns the max of the metric. +// MaxDigits returns the max series number of digits. func (m Metric) MaxDigits() int { - max := int(math.Max(float64(m.OK), float64(m.Fault))) - s := fmt.Sprintf("%d", max) + + s := fmt.Sprintf("%d", m.Max()) return len(s) } +// Max returns the max of the series. +func (m Metric) Max() int { + return int(math.Max(float64(m.OK), float64(m.Fault))) +} + // Sum returns the sum of the metrics. func (m Metric) Sum() int { return m.OK + m.Fault @@ -40,16 +46,23 @@ func (m Metric) Sum() int { type SparkLine struct { *Component - data []Metric + data []Metric + multiSeries bool } // NewSparkLine returns a new graph. func NewSparkLine(id string) *SparkLine { return &SparkLine{ - Component: NewComponent(id), + Component: NewComponent(id), + multiSeries: true, } } +// SetSingleSeries indicates multi series are in effect or not. +func (s *SparkLine) SetMultiSeries(b bool) { + s.multiSeries = b +} + // Add adds a metric. func (s *SparkLine) Add(m Metric) { s.mx.Lock() @@ -84,13 +97,17 @@ func (s *SparkLine) Draw(screen tcell.Screen) { idx = len(s.data) - rect.Dx()/2 } - scale := float64(len(sparks)) * float64((rect.Dy() - pad)) / float64(max) + factor := 2 + if !s.multiSeries { + factor = 1 + } + scale := float64(len(sparks)*(rect.Dy()-pad)/factor) / float64(max) c1, c2 := s.colorForSeries() for _, d := range s.data[idx:] { b := toBlocks(d, scale) cY := rect.Max.Y - pad - s.drawBlock(screen, cX, cY, b.oks, c1) - s.drawBlock(screen, cX, cY, b.errs, c2) + cY = s.drawBlock(rect, screen, cX, cY, b.oks, c1) + _ = s.drawBlock(rect, screen, cX, cY, b.errs, c2) cX += 2 } @@ -103,16 +120,25 @@ func (s *SparkLine) Draw(screen tcell.Screen) { } } -func (s *SparkLine) drawBlock(screen tcell.Screen, x, y int, b block, c tcell.Color) { +func (s *SparkLine) drawBlock(r image.Rectangle, screen tcell.Screen, x, y int, b block, c tcell.Color) int { style := tcell.StyleDefault.Foreground(c).Background(s.bgColor) + zeroY := r.Max.Y - r.Dy() for i := 0; i < b.full; i++ { screen.SetContent(x, y, sparks[len(sparks)-1], nil, style) y-- + if y <= zeroY { + break + } } if b.partial != 0 { screen.SetContent(x, y, b.partial, nil, style) + if b.full == 0 { + y-- + } } + + return y } func (s *SparkLine) cutSet(width int) { @@ -128,8 +154,9 @@ func (s *SparkLine) cutSet(width int) { func (s *SparkLine) computeMax() int { var max int for _, d := range s.data { - if max < d.OK { - max = d.OK + m := d.Max() + if max < m { + max = m } } @@ -140,18 +167,17 @@ func toBlocks(m Metric, scale float64) blocks { if m.Sum() <= 0 { return blocks{} } - return blocks{oks: makeBlocks(m.OK, false, scale), errs: makeBlocks(m.Fault, true, scale)} + return blocks{oks: makeBlocks(m.OK, scale), errs: makeBlocks(m.Fault, scale)} } -func makeBlocks(v int, isErr bool, scale float64) block { +func makeBlocks(v int, 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 + p, b := scaled%len(sparks), block{full: scaled / len(sparks)} + if b.full == 0 && v > 0 && p == 0 { + p = 4 } - if part > 0 { - b.partial = sparks[part-1] + if v > 0 && p >= 0 && p < len(sparks) { + b.partial = sparks[p] } return b diff --git a/internal/tchart/sparkline_int_test.go b/internal/tchart/sparkline_int_test.go new file mode 100644 index 00000000..ac3278c8 --- /dev/null +++ b/internal/tchart/sparkline_int_test.go @@ -0,0 +1,131 @@ +package tchart + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCutSet(t *testing.T) { + uu := map[string]struct { + mm []Metric + w, e int + }{ + "empty": { + w: 10, + e: 0, + }, + "at": { + mm: make([]Metric, 10), + w: 10, + e: 10, + }, + "under": { + mm: make([]Metric, 5), + w: 10, + e: 5, + }, + "over": { + mm: make([]Metric, 10), + w: 5, + e: 5, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + s := NewSparkLine("s") + assert.False(t, s.IsDial()) + + for _, m := range u.mm { + s.Add(m) + } + s.cutSet(u.w) + assert.Equal(t, u.e, len(s.data)) + }) + } +} + +func TestToBlocks(t *testing.T) { + uu := map[string]struct { + m Metric + s float64 + e blocks + }{ + "empty": { + e: blocks{}, + }, + "max_ok": { + m: Metric{OK: 100, Fault: 10}, + s: 0.5, + e: blocks{ + oks: block{full: 6, partial: sparks[2]}, + errs: block{full: 0, partial: sparks[5]}, + }, + }, + "max_fault": { + m: Metric{OK: 10, Fault: 100}, + s: 0.5, + e: blocks{ + oks: block{full: 0, partial: sparks[5]}, + errs: block{full: 6, partial: sparks[2]}, + }, + }, + "over": { + m: Metric{OK: 22, Fault: 999}, + s: float64(8*20) / float64(999), + e: blocks{ + oks: block{full: 0, partial: sparks[4]}, + errs: block{full: 20, partial: sparks[0]}, + }, + }, + } + + for k := range uu { + u := uu[k] + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, toBlocks(u.m, u.s)) + }) + } +} + +func TestComputeMax(t *testing.T) { + uu := map[string]struct { + mm []Metric + e int + }{ + "empty": { + e: 0, + }, + "max_ok": { + mm: []Metric{{OK: 100, Fault: 10}}, + e: 100, + }, + "max_fault": { + mm: []Metric{{OK: 100, Fault: 1000}}, + e: 1000, + }, + "many": { + mm: []Metric{ + {OK: 100, Fault: 1000}, + {OK: 110, Fault: 1010}, + {OK: 120, Fault: 1020}, + {OK: 130, Fault: 1030}, + {OK: 140, Fault: 1040}, + }, + e: 1040, + }, + } + + for k := range uu { + u := uu[k] + s := NewSparkLine("s") + for _, m := range u.mm { + s.Add(m) + } + t.Run(k, func(t *testing.T) { + assert.Equal(t, u.e, s.computeMax()) + }) + } +} diff --git a/internal/ui/indicator.go b/internal/ui/indicator.go index 5c89fbba..74c191e5 100644 --- a/internal/ui/indicator.go +++ b/internal/ui/indicator.go @@ -45,7 +45,7 @@ func (s *StatusIndicator) StylesChanged(styles *config.Styles) { s.SetTextColor(styles.FgColor()) } -const statusIndicatorFmt = "[orange::b]K9s [aqua::]%s [white::]%s:%s:%s [lawngreen::]%s%%[white::]::[darkturquoise::]%s%%" +const statusIndicatorFmt = "[orange::b]K9s [aqua::]%s [white::]%s:%s:%s [lawngreen::]%s[white::]::[darkturquoise::]%s" // ClusterInfoUpdated notifies the cluster meta was updated. func (s *StatusIndicator) ClusterInfoUpdated(data model.ClusterMeta) { @@ -56,8 +56,8 @@ func (s *StatusIndicator) ClusterInfoUpdated(data model.ClusterMeta) { data.Cluster, data.User, data.K8sVer, - render.AsPerc(data.Cpu), - render.AsPerc(data.Mem), + render.ToPerc(data.Cpu), + render.ToPerc(data.Mem), )) }) } diff --git a/internal/ui/select_table.go b/internal/ui/select_table.go index 57a681a2..1002698a 100644 --- a/internal/ui/select_table.go +++ b/internal/ui/select_table.go @@ -9,10 +9,9 @@ import ( type SelectTable struct { *tview.Table - model Tabular - selectedRow int - selectedFn func(string) string - marks map[string]struct{} + model Tabular + selectedFn func(string) string + marks map[string]struct{} } // SetModel sets the table model. @@ -69,7 +68,8 @@ func (s *SelectTable) GetSelectedItem() string { // GetSelectedCell returns the content of a cell for the currently selected row. func (s *SelectTable) GetSelectedCell(col int) string { - return TrimCell(s, s.selectedRow, col) + r, _ := s.GetSelection() + return TrimCell(s, r, col) } // SetSelectedFn defines a function that cleanse the current selection. @@ -79,7 +79,8 @@ func (s *SelectTable) SetSelectedFn(f func(string) string) { // GetSelectedRowIndex fetch the currently selected row index. func (s *SelectTable) GetSelectedRowIndex() int { - return s.selectedRow + r, _ := s.GetSelection() + return r } // SelectRow select a given row by index. @@ -93,14 +94,14 @@ func (s *SelectTable) SelectRow(r int, broadcast bool) { // UpdateSelection refresh selected row. func (s *SelectTable) updateSelection(broadcast bool) { - s.SelectRow(s.selectedRow, broadcast) + r, _ := s.GetSelection() + s.SelectRow(r, broadcast) } func (s *SelectTable) selectionChanged(r, c int) { if r < 0 { return } - s.selectedRow = r cell := s.GetCell(r, c) s.SetSelectedStyle(tcell.ColorBlack, cell.Color, tcell.AttrBold) } diff --git a/internal/ui/table.go b/internal/ui/table.go index bb515b1c..2923523b 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -50,10 +50,9 @@ type Table struct { func NewTable(gvr client.GVR) *Table { return &Table{ SelectTable: &SelectTable{ - Table: tview.NewTable(), - model: model.NewTable(gvr), - selectedRow: 1, - marks: make(map[string]struct{}), + Table: tview.NewTable(), + model: model.NewTable(gvr), + marks: make(map[string]struct{}), }, gvr: gvr, actions: make(KeyActions), @@ -71,7 +70,7 @@ func (t *Table) Init(ctx context.Context) { t.SetSelectable(true, false) t.SetSelectionChangedFunc(t.selectionChanged) t.SetBackgroundColor(tcell.ColorDefault) - + t.Select(1, 0) t.hasMetrics = false if mx, ok := ctx.Value(internal.KeyHasMetrics).(bool); ok { t.hasMetrics = mx diff --git a/internal/view/help.go b/internal/view/help.go index 959f4580..3f935a7d 100644 --- a/internal/view/help.go +++ b/internal/view/help.go @@ -159,10 +159,10 @@ func (h *Help) showNav() model.MenuHints { }, { Mnemonic: "Ctrl-b", - Description: "Page Down"}, + Description: "Page Up"}, { Mnemonic: "Ctrl-f", - Description: "Page Up", + Description: "Page Down", }, { Mnemonic: "h", diff --git a/internal/view/pulse.go b/internal/view/pulse.go index 1c568c66..38be031b 100644 --- a/internal/view/pulse.go +++ b/internal/view/pulse.go @@ -80,25 +80,26 @@ func (p *Pulse) Init(ctx context.Context) error { } p.charts = []Grapheable{ - p.makeGA(image.Point{X: 0, Y: 0}, image.Point{X: 4, Y: 2}, "apps/v1/deployments"), - p.makeGA(image.Point{X: 0, Y: 2}, image.Point{X: 4, Y: 2}, "apps/v1/replicasets"), - p.makeGA(image.Point{X: 0, Y: 4}, image.Point{X: 4, Y: 2}, "apps/v1/statefulsets"), - p.makeGA(image.Point{X: 0, Y: 6}, image.Point{X: 4, Y: 2}, "apps/v1/daemonsets"), - p.makeSP(image.Point{X: 4, Y: 0}, image.Point{X: 3, Y: 4}, "v1/pods"), - p.makeSP(image.Point{X: 4, Y: 4}, image.Point{X: 3, Y: 4}, "v1/events"), - p.makeSP(image.Point{X: 7, Y: 0}, image.Point{X: 3, Y: 4}, "batch/v1/jobs"), - p.makeSP(image.Point{X: 7, Y: 4}, image.Point{X: 3, Y: 4}, "v1/persistentvolumes"), + p.makeGA(image.Point{X: 0, Y: 0}, image.Point{X: 3, Y: 2}, "apps/v1/deployments"), + p.makeGA(image.Point{X: 0, Y: 2}, image.Point{X: 3, Y: 2}, "apps/v1/replicasets"), + p.makeGA(image.Point{X: 0, Y: 4}, image.Point{X: 3, Y: 2}, "apps/v1/statefulsets"), + p.makeGA(image.Point{X: 0, Y: 6}, image.Point{X: 3, Y: 2}, "apps/v1/daemonsets"), + p.makeSP(true, image.Point{X: 3, Y: 0}, image.Point{X: 3, Y: 4}, "v1/pods"), + p.makeSP(true, image.Point{X: 3, Y: 4}, image.Point{X: 3, Y: 4}, "v1/events"), + p.makeSP(true, image.Point{X: 6, Y: 0}, image.Point{X: 3, Y: 4}, "batch/v1/jobs"), + p.makeSP(true, image.Point{X: 6, Y: 4}, image.Point{X: 3, Y: 4}, "v1/persistentvolumes"), } if p.app.Conn().HasMetrics() { p.charts = append(p.charts, - p.makeSP(image.Point{X: 10, Y: 0}, image.Point{X: 2, Y: 4}, "cpu"), - p.makeSP(image.Point{X: 10, Y: 4}, image.Point{X: 2, Y: 4}, "mem"), + p.makeSP(false, image.Point{X: 9, Y: 0}, image.Point{X: 2, Y: 4}, "cpu"), + p.makeSP(false, image.Point{X: 9, Y: 4}, image.Point{X: 2, Y: 4}, "mem"), ) } p.bindKeys() p.model.AddListener(p) p.app.SetFocus(p.charts[0]) p.app.Styles.AddListener(p) + p.StylesChanged(p.app.Styles) return nil } @@ -135,9 +136,9 @@ func (p *Pulse) PulseChanged(c *health.Check) { gvr := client.NewGVR(c.GVR) switch c.GVR { case "cpu": - v.SetLegend(fmt.Sprintf(" %s - %dm", strings.Title(gvr.R()), c.Tally(health.OK))) + v.SetLegend(fmt.Sprintf(" %s(%dm)", strings.Title(gvr.R()), c.Tally(health.OK))) case "mem": - v.SetLegend(fmt.Sprintf(" %s - %dMi", strings.Title(gvr.R()), c.Tally(health.OK))) + v.SetLegend(fmt.Sprintf(" %s(%dMi)", strings.Title(gvr.R()), c.Tally(health.OK))) default: nn := v.GetSeriesColorNames() if c.Tally(health.OK) == 0 { @@ -146,7 +147,7 @@ func (p *Pulse) PulseChanged(c *health.Check) { if c.Tally(health.Toast) == 0 { nn[1] = "gray" } - v.SetLegend(fmt.Sprintf(" %s - [%s::]%d/[%s::b]%d[-::]", + v.SetLegend(fmt.Sprintf(" %s([%s::]%d[white::]:[%s::b]%d[-::])", strings.Title(gvr.R()), nn[0], c.Tally(health.OK), @@ -296,7 +297,7 @@ func (p *Pulse) nextFocusCmd(direction int) func(evt *tcell.EventKey) *tcell.Eve } } -func (p *Pulse) makeSP(loc image.Point, span image.Point, gvr string) *tchart.SparkLine { +func (p *Pulse) makeSP(multi bool, loc image.Point, span image.Point, gvr string) *tchart.SparkLine { s := tchart.NewSparkLine(gvr) s.SetBackgroundColor(p.app.Styles.Charts().BgColor.Color()) s.SetBorderPadding(0, 1, 0, 1) @@ -307,6 +308,9 @@ func (p *Pulse) makeSP(loc image.Point, span image.Point, gvr string) *tchart.Sp } s.SetLegend(fmt.Sprintf(" %s ", strings.Title(client.NewGVR(gvr).R()))) s.SetInputCapture(p.keyboard) + if !multi { + s.SetMultiSeries(multi) + } p.AddItem(s, loc.X, loc.Y, span.X, span.Y, 0, 0, true) return s @@ -323,7 +327,7 @@ func (p *Pulse) makeGA(loc image.Point, span image.Point, gvr string) *tchart.Ga } g.SetLegend(fmt.Sprintf(" %s ", strings.Title(client.NewGVR(gvr).R()))) g.SetInputCapture(p.keyboard) - p.AddItem(g, loc.X, loc.Y, span.X, span.Y, 0, 0, true) + p.AddItem(g, loc.X, loc.Y, span.X, span.Y, span.X, span.Y, true) return g }