mirror of
https://github.com/hibiken/asynq.git
synced 2025-10-03 05:12:01 +08:00
(cli): task modal
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
package dash
|
package dash
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
@@ -90,6 +91,7 @@ func Run(opts Options) {
|
|||||||
// channels to send/receive data fetched asynchronously
|
// channels to send/receive data fetched asynchronously
|
||||||
errorCh = make(chan error)
|
errorCh = make(chan error)
|
||||||
queueCh = make(chan *asynq.QueueInfo)
|
queueCh = make(chan *asynq.QueueInfo)
|
||||||
|
taskCh = make(chan *asynq.TaskInfo)
|
||||||
queuesCh = make(chan []*asynq.QueueInfo)
|
queuesCh = make(chan []*asynq.QueueInfo)
|
||||||
groupsCh = make(chan []*asynq.GroupInfo)
|
groupsCh = make(chan []*asynq.GroupInfo)
|
||||||
tasksCh = make(chan []*asynq.TaskInfo)
|
tasksCh = make(chan []*asynq.TaskInfo)
|
||||||
@@ -102,6 +104,7 @@ func Run(opts Options) {
|
|||||||
opts,
|
opts,
|
||||||
errorCh,
|
errorCh,
|
||||||
queueCh,
|
queueCh,
|
||||||
|
taskCh,
|
||||||
queuesCh,
|
queuesCh,
|
||||||
groupsCh,
|
groupsCh,
|
||||||
tasksCh,
|
tasksCh,
|
||||||
@@ -156,6 +159,11 @@ func Run(opts Options) {
|
|||||||
go fetchTasks(inspector, state.selectedQueue.Queue, state.taskState,
|
go fetchTasks(inspector, state.selectedQueue.Queue, state.taskState,
|
||||||
taskPageSize(s), state.pageNum, tasksCh, errorCh)
|
taskPageSize(s), state.pageNum, tasksCh, errorCh)
|
||||||
}
|
}
|
||||||
|
// if the task modal is open, fetch the selected task's info
|
||||||
|
if state.taskID != "" {
|
||||||
|
go fetchTaskInfo(inspector, state.selectedQueue.Queue, state.taskID, taskCh, errorCh)
|
||||||
|
}
|
||||||
|
|
||||||
case viewTypeRedis:
|
case viewTypeRedis:
|
||||||
go fetchRedisInfo(redisInfoCh, errorCh)
|
go fetchRedisInfo(redisInfoCh, errorCh)
|
||||||
}
|
}
|
||||||
@@ -189,13 +197,22 @@ func Run(opts Options) {
|
|||||||
}
|
}
|
||||||
d.draw(&state)
|
d.draw(&state)
|
||||||
|
|
||||||
|
case t := <-taskCh:
|
||||||
|
state.selectedTask = t
|
||||||
|
state.err = nil
|
||||||
|
d.draw(&state)
|
||||||
|
|
||||||
case redisInfo := <-redisInfoCh:
|
case redisInfo := <-redisInfoCh:
|
||||||
state.redisInfo = *redisInfo
|
state.redisInfo = *redisInfo
|
||||||
state.err = nil
|
state.err = nil
|
||||||
d.draw(&state)
|
d.draw(&state)
|
||||||
|
|
||||||
case err := <-errorCh:
|
case err := <-errorCh:
|
||||||
state.err = err
|
if errors.Is(err, asynq.ErrTaskNotFound) {
|
||||||
|
state.selectedTask = nil
|
||||||
|
} else {
|
||||||
|
state.err = err
|
||||||
|
}
|
||||||
d.draw(&state)
|
d.draw(&state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
@@ -445,16 +447,74 @@ func drawTaskModal(d *ScreenDrawer, state *State) {
|
|||||||
if state.taskID == "" {
|
if state.taskID == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
task := state.selectedTask
|
||||||
|
if task == nil {
|
||||||
|
// task no longer found
|
||||||
|
contents := []*rowContent{
|
||||||
|
{" === Task Summary ===", baseStyle.Bold(true)},
|
||||||
|
{"", baseStyle},
|
||||||
|
{fmt.Sprintf(" Task %q no longer exists", state.taskID), baseStyle},
|
||||||
|
}
|
||||||
|
withModal(d, contents)
|
||||||
|
return
|
||||||
|
}
|
||||||
contents := []*rowContent{
|
contents := []*rowContent{
|
||||||
{" === Task Summary ===", baseStyle.Bold(true)},
|
{" === Task Summary ===", baseStyle.Bold(true)},
|
||||||
{"", baseStyle},
|
{"", baseStyle},
|
||||||
{" ID: xxxxx", baseStyle},
|
{fmt.Sprintf(" ID: %s", task.ID), baseStyle},
|
||||||
{" Type: xxxxx", baseStyle},
|
{fmt.Sprintf(" Type: %s", task.Type), baseStyle},
|
||||||
{" State: xxxx", baseStyle},
|
{fmt.Sprintf(" State: %s", task.State.String()), baseStyle},
|
||||||
|
{fmt.Sprintf(" Queue: %s", task.Queue), baseStyle},
|
||||||
|
{fmt.Sprintf(" Retried: %d/%d", task.Retried, task.MaxRetry), baseStyle},
|
||||||
|
}
|
||||||
|
if task.LastErr != "" {
|
||||||
|
contents = append(contents, &rowContent{
|
||||||
|
fmt.Sprintf(" Last Failure: %s", task.LastErr),
|
||||||
|
baseStyle,
|
||||||
|
})
|
||||||
|
contents = append(contents, &rowContent{
|
||||||
|
fmt.Sprintf(" Last Failure Time: %v", task.LastFailedAt),
|
||||||
|
baseStyle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if !task.NextProcessAt.IsZero() {
|
||||||
|
contents = append(contents, &rowContent{
|
||||||
|
fmt.Sprintf(" Next Process Time: %v", task.NextProcessAt),
|
||||||
|
baseStyle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if !task.CompletedAt.IsZero() {
|
||||||
|
contents = append(contents, &rowContent{
|
||||||
|
fmt.Sprintf(" Completion Time: %v", task.CompletedAt),
|
||||||
|
baseStyle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if isPrintable(task.Payload) {
|
||||||
|
contents = append(contents, &rowContent{
|
||||||
|
fmt.Sprintf("Payload: %s", string(task.Payload)),
|
||||||
|
baseStyle,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
withModal(d, contents)
|
withModal(d, contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reports whether the given byte slice is printable (i.e. human readable)
|
||||||
|
func isPrintable(data []byte) bool {
|
||||||
|
if !utf8.Valid(data) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
isAllSpace := true
|
||||||
|
for _, r := range string(data) {
|
||||||
|
if !unicode.IsGraphic(r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !unicode.IsSpace(r) {
|
||||||
|
isAllSpace = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !isAllSpace
|
||||||
|
}
|
||||||
|
|
||||||
type rowContent struct {
|
type rowContent struct {
|
||||||
s string // should not include newline
|
s string // should not include newline
|
||||||
style tcell.Style
|
style tcell.Style
|
||||||
|
@@ -14,6 +14,7 @@ type fetcher interface {
|
|||||||
fetchQueues()
|
fetchQueues()
|
||||||
fetchQueueInfo(qname string)
|
fetchQueueInfo(qname string)
|
||||||
fetchRedisInfo()
|
fetchRedisInfo()
|
||||||
|
fetchTaskInfo(qname, taskID string)
|
||||||
fetchTasks(qname string, taskState asynq.TaskState, pageSize, pageNum int)
|
fetchTasks(qname string, taskState asynq.TaskState, pageSize, pageNum int)
|
||||||
fetchAggregatingTasks(qname, group string, pageSize, pageNum int)
|
fetchAggregatingTasks(qname, group string, pageSize, pageNum int)
|
||||||
fetchGroups(qname string)
|
fetchGroups(qname string)
|
||||||
@@ -25,6 +26,7 @@ type dataFetcher struct {
|
|||||||
|
|
||||||
errorCh chan<- error
|
errorCh chan<- error
|
||||||
queueCh chan<- *asynq.QueueInfo
|
queueCh chan<- *asynq.QueueInfo
|
||||||
|
taskCh chan<- *asynq.TaskInfo
|
||||||
queuesCh chan<- []*asynq.QueueInfo
|
queuesCh chan<- []*asynq.QueueInfo
|
||||||
groupsCh chan<- []*asynq.GroupInfo
|
groupsCh chan<- []*asynq.GroupInfo
|
||||||
tasksCh chan<- []*asynq.TaskInfo
|
tasksCh chan<- []*asynq.TaskInfo
|
||||||
@@ -169,3 +171,21 @@ func fetchTasks(i *asynq.Inspector, qname string, taskState asynq.TaskState, pag
|
|||||||
}
|
}
|
||||||
tasksCh <- tasks
|
tasksCh <- tasks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *dataFetcher) fetchTaskInfo(qname, taskID string) {
|
||||||
|
var (
|
||||||
|
i = f.inspector
|
||||||
|
taskCh = f.taskCh
|
||||||
|
errorCh = f.errorCh
|
||||||
|
)
|
||||||
|
go fetchTaskInfo(i, qname, taskID, taskCh, errorCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchTaskInfo(i *asynq.Inspector, qname, taskID string, taskCh chan<- *asynq.TaskInfo, errorCh chan<- error) {
|
||||||
|
info, err := i.GetTaskInfo(qname, taskID)
|
||||||
|
if err != nil {
|
||||||
|
errorCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
taskCh <- info
|
||||||
|
}
|
||||||
|
@@ -210,9 +210,10 @@ func (h *keyEventHandler) enterKeyQueueDetails() {
|
|||||||
d.draw(state)
|
d.draw(state)
|
||||||
} else if !shouldShowGroupTable(state) && state.taskTableRowIdx != 0 {
|
} else if !shouldShowGroupTable(state) && state.taskTableRowIdx != 0 {
|
||||||
task := state.tasks[state.taskTableRowIdx-1]
|
task := state.tasks[state.taskTableRowIdx-1]
|
||||||
state.taskID = task.ID
|
|
||||||
state.selectedTask = task
|
state.selectedTask = task
|
||||||
// TODO: go fetch task info
|
state.taskID = task.ID
|
||||||
|
f.fetchTaskInfo(state.selectedQueue.Queue, task.ID)
|
||||||
|
h.resetTicker()
|
||||||
d.draw(state)
|
d.draw(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -183,6 +183,7 @@ type fakeFetcher struct{}
|
|||||||
func (f *fakeFetcher) fetchQueues() {}
|
func (f *fakeFetcher) fetchQueues() {}
|
||||||
func (f *fakeFetcher) fetchQueueInfo(qname string) {}
|
func (f *fakeFetcher) fetchQueueInfo(qname string) {}
|
||||||
func (f *fakeFetcher) fetchRedisInfo() {}
|
func (f *fakeFetcher) fetchRedisInfo() {}
|
||||||
|
func (f *fakeFetcher) fetchTaskInfo(qname, taskID string) {}
|
||||||
func (f *fakeFetcher) fetchTasks(qname string, taskState asynq.TaskState, pageSize, pageNum int) {}
|
func (f *fakeFetcher) fetchTasks(qname string, taskState asynq.TaskState, pageSize, pageNum int) {}
|
||||||
func (f *fakeFetcher) fetchAggregatingTasks(qname, group string, pageSize, pageNum int) {}
|
func (f *fakeFetcher) fetchAggregatingTasks(qname, group string, pageSize, pageNum int) {}
|
||||||
func (f *fakeFetcher) fetchGroups(qname string) {}
|
func (f *fakeFetcher) fetchGroups(qname string) {}
|
||||||
|
Reference in New Issue
Block a user