mirror of
https://github.com/hibiken/asynq.git
synced 2025-09-19 13:21:58 +08:00
(cli): Show queue info banner in dash
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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 {
|
||||||
@@ -206,78 +218,11 @@ func drawQueueTable(d *ScreenDrawer, style tcell.Style, state *State) {
|
|||||||
{"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)
|
|
||||||
|
func drawQueueTable(d *ScreenDrawer, style tcell.Style, state *State) {
|
||||||
|
drawTable(d, style, queueColumnConfigs, state.queues, state.rowIdx-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
type columnAlignment int
|
func drawQueueInfoBanner(d *ScreenDrawer, style tcell.Style, state *State) {
|
||||||
|
drawTable(d, style, queueColumnConfigs, []*asynq.QueueInfo{state.selectedQueue}, -1 /* no highlited row */)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
70
tools/asynq/cmd/dash/table.go
Normal file
70
tools/asynq/cmd/dash/table.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user