(cli): Show queue info banner in dash

This commit is contained in:
Ken Hibino
2022-05-18 06:08:22 -07:00
parent b6303876b5
commit b78ed063ff
3 changed files with 106 additions and 90 deletions

View File

@@ -32,7 +32,8 @@ type State struct {
err error err error
rowIdx int // highlighted row rowIdx int // highlighted row
selectedQueue string // name of the selected queue
selectedQueue *asynq.QueueInfo // queue shown on queue details view
view viewType // current view type view viewType // current view type
prevView viewType // to support "go back" prevView viewType // to support "go back"
@@ -137,7 +138,7 @@ func Run(opts Options) {
drawDash(s, baseStyle, &state, opts) drawDash(s, baseStyle, &state, opts)
} else if ev.Key() == tcell.KeyEnter { } else if ev.Key() == tcell.KeyEnter {
if state.view == viewTypeQueues && state.rowIdx != 0 { if state.view == viewTypeQueues && state.rowIdx != 0 {
state.selectedQueue = state.queues[state.rowIdx-1].Queue state.selectedQueue = state.queues[state.rowIdx-1]
state.view = viewTypeQueueDetails state.view = viewTypeQueueDetails
drawDash(s, baseStyle, &state, opts) drawDash(s, baseStyle, &state, opts)
} }

View File

@@ -27,9 +27,9 @@ func drawDash(s tcell.Screen, style tcell.Style, state *State, opts Options) {
d.NL() // empty line d.NL() // empty line
drawQueueTable(d, style, state) drawQueueTable(d, style, state)
case viewTypeQueueDetails: case viewTypeQueueDetails:
d.Println(fmt.Sprintf("=== Queues > %s ===", state.selectedQueue), style) d.Println(fmt.Sprintf("=== Queues > %s ===", state.selectedQueue.Queue), style)
d.NL() d.NL()
// TODO: draw body drawQueueInfoBanner(d, style, state)
case viewTypeServers: case viewTypeServers:
d.Println("=== Servers ===", style.Bold(true)) d.Println("=== Servers ===", style.Bold(true))
d.NL() // empty line d.NL() // empty line
@@ -173,6 +173,19 @@ func maxwidth(names []string) int {
return max return max
} }
// rpad adds padding to the right of a string.
func rpad(s string, padding int) string {
tmpl := fmt.Sprintf("%%-%ds ", padding)
return fmt.Sprintf(tmpl, s)
}
// lpad adds padding to the left of a string.
func lpad(s string, padding int) string {
tmpl := fmt.Sprintf("%%%ds ", padding)
return fmt.Sprintf(tmpl, s)
}
// ByteCount converts the given bytes into human readable string // ByteCount converts the given bytes into human readable string
func ByteCount(b int64) string { func ByteCount(b int64) string {
const unit = 1000 const unit = 1000
@@ -189,8 +202,7 @@ func ByteCount(b int64) string {
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp]) return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
} }
func drawQueueTable(d *ScreenDrawer, style tcell.Style, state *State) { var queueColumnConfigs = []*columnConfig[*asynq.QueueInfo]{
colConfigs := []*columnConfig[*asynq.QueueInfo]{
{"Queue", alignLeft, func(q *asynq.QueueInfo) string { return q.Queue }}, {"Queue", alignLeft, func(q *asynq.QueueInfo) string { return q.Queue }},
{"State", alignLeft, func(q *asynq.QueueInfo) string { {"State", alignLeft, func(q *asynq.QueueInfo) string {
if q.Paused { if q.Paused {
@@ -205,79 +217,12 @@ func drawQueueTable(d *ScreenDrawer, style tcell.Style, state *State) {
{"Processed", alignRight, func(q *asynq.QueueInfo) string { return strconv.Itoa(q.Processed) }}, {"Processed", alignRight, func(q *asynq.QueueInfo) string { return strconv.Itoa(q.Processed) }},
{"Failed", alignRight, func(q *asynq.QueueInfo) string { return strconv.Itoa(q.Failed) }}, {"Failed", alignRight, func(q *asynq.QueueInfo) string { return strconv.Itoa(q.Failed) }},
{"ErrorRate", alignRight, func(q *asynq.QueueInfo) string { return "0.23%" /* TODO: implement this */ }}, {"ErrorRate", alignRight, func(q *asynq.QueueInfo) string { return "0.23%" /* TODO: implement this */ }},
}
drawTable(d, style, colConfigs, state.queues, state.rowIdx-1)
} }
type columnAlignment int func drawQueueTable(d *ScreenDrawer, style tcell.Style, state *State) {
drawTable(d, style, queueColumnConfigs, state.queues, state.rowIdx-1)
const (
alignRight columnAlignment = iota
alignLeft
)
type columnConfig[V any] struct {
name string
alignment columnAlignment
displayFn func(v V) string
} }
type column[V any] struct { func drawQueueInfoBanner(d *ScreenDrawer, style tcell.Style, state *State) {
*columnConfig[V] drawTable(d, style, queueColumnConfigs, []*asynq.QueueInfo{state.selectedQueue}, -1 /* no highlited row */)
width int
}
// Helper to draw a table.
func drawTable[V any](d *ScreenDrawer, style tcell.Style, configs []*columnConfig[V], data []V, highlightRowIdx int) {
const colBuffer = 4 // extra buffer between columns
cols := make([]*column[V], len(configs))
for i, cfg := range configs {
cols[i] = &column[V]{cfg, runewidth.StringWidth(cfg.name)}
}
// adjust the column width to accommodate the widest value.
for _, v := range data {
for _, col := range cols {
if w := runewidth.StringWidth(col.displayFn(v)); col.width < w {
col.width = w
}
}
}
// print header
headerStyle := style.Background(tcell.ColorDimGray).Foreground(tcell.ColorWhite)
for _, col := range cols {
if col.alignment == alignLeft {
d.Print(rpad(col.name, col.width+colBuffer), headerStyle)
} else {
d.Print(lpad(col.name, col.width+colBuffer), headerStyle)
}
}
d.FillLine(' ', headerStyle)
// print body
for i, v := range data {
rowStyle := style
if highlightRowIdx == i {
rowStyle = style.Background(tcell.ColorDarkOliveGreen)
}
for _, col := range cols {
if col.alignment == alignLeft {
d.Print(rpad(col.displayFn(v), col.width+colBuffer), rowStyle)
} else {
d.Print(lpad(col.displayFn(v), col.width+colBuffer), rowStyle)
}
}
d.FillLine(' ', rowStyle)
}
}
// rpad adds padding to the right of a string.
func rpad(s string, padding int) string {
tmpl := fmt.Sprintf("%%-%ds ", padding)
return fmt.Sprintf(tmpl, s)
}
// lpad adds padding to the left of a string.
func lpad(s string, padding int) string {
tmpl := fmt.Sprintf("%%%ds ", padding)
return fmt.Sprintf(tmpl, s)
} }

View File

@@ -0,0 +1,70 @@
// Copyright 2022 Kentaro Hibino. All rights reserved.
// Use of this source code is governed by a MIT license
// that can be found in the LICENSE file.
package dash
import (
"github.com/gdamore/tcell/v2"
"github.com/mattn/go-runewidth"
)
type columnAlignment int
const (
alignRight columnAlignment = iota
alignLeft
)
type columnConfig[V any] struct {
name string
alignment columnAlignment
displayFn func(v V) string
}
type column[V any] struct {
*columnConfig[V]
width int
}
// Helper to draw a table.
func drawTable[V any](d *ScreenDrawer, style tcell.Style, configs []*columnConfig[V], data []V, highlightRowIdx int) {
const colBuffer = 4 // extra buffer between columns
cols := make([]*column[V], len(configs))
for i, cfg := range configs {
cols[i] = &column[V]{cfg, runewidth.StringWidth(cfg.name)}
}
// adjust the column width to accommodate the widest value.
for _, v := range data {
for _, col := range cols {
if w := runewidth.StringWidth(col.displayFn(v)); col.width < w {
col.width = w
}
}
}
// print header
headerStyle := style.Background(tcell.ColorDimGray).Foreground(tcell.ColorWhite)
for _, col := range cols {
if col.alignment == alignLeft {
d.Print(rpad(col.name, col.width+colBuffer), headerStyle)
} else {
d.Print(lpad(col.name, col.width+colBuffer), headerStyle)
}
}
d.FillLine(' ', headerStyle)
// print body
for i, v := range data {
rowStyle := style
if highlightRowIdx == i {
rowStyle = style.Background(tcell.ColorDarkOliveGreen)
}
for _, col := range cols {
if col.alignment == alignLeft {
d.Print(rpad(col.displayFn(v), col.width+colBuffer), rowStyle)
} else {
d.Print(lpad(col.displayFn(v), col.width+colBuffer), rowStyle)
}
}
d.FillLine(' ', rowStyle)
}
}