From bcf1cfd3936b249c38ac650ca8415ddf0a6ec8a5 Mon Sep 17 00:00:00 2001 From: Shinto C V Date: Mon, 29 Jan 2024 23:08:40 +0530 Subject: [PATCH] Add help for Admin actions Add toast for failures in admin actions --- README.md | 1 + internal/dev/dev.go | 1 - internal/tui/components/app/app.go | 52 ++++++++++++++-------------- internal/tui/components/page/page.go | 6 ++-- internal/tui/nomad/pages.go | 8 ++++- internal/tui/nomad/taskadmin.go | 41 +++++++++++++++++----- internal/tui/nomad/util.go | 1 + internal/tui/style/style.go | 2 -- 8 files changed, 69 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 9b35a387..e8a6d378 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ An efficient terminal application/TUI for interacting with your [HashiCorp Nomad](https://www.nomadproject.io/) cluster. - Browse jobs, allocations, and tasks +- Restart tasks - Live tail logs - Tail global or targeted events - Exec to interact with running tasks diff --git a/internal/dev/dev.go b/internal/dev/dev.go index 94fb6a24..32c56498 100644 --- a/internal/dev/dev.go +++ b/internal/dev/dev.go @@ -26,4 +26,3 @@ func createDebug(path string) func(string) { } var Debug = createDebug("wander.log") -var MyDebug = createDebug("/tmp/mydebug.log") diff --git a/internal/tui/components/app/app.go b/internal/tui/components/app/app.go index 3a367e06..1a741628 100644 --- a/internal/tui/components/app/app.go +++ b/internal/tui/components/app/app.go @@ -2,7 +2,6 @@ package app import ( "fmt" - "github.com/robinovitch61/wander/internal/tui/components/toast" "os" "os/exec" "path" @@ -14,6 +13,7 @@ import ( "github.com/hashicorp/nomad/api" "github.com/itchyny/gojq" "github.com/robinovitch61/wander/internal/dev" + "github.com/robinovitch61/wander/internal/tui/components/toast" "github.com/robinovitch61/wander/internal/tui/components/header" "github.com/robinovitch61/wander/internal/tui/components/page" "github.com/robinovitch61/wander/internal/tui/constants" @@ -63,14 +63,12 @@ type Config struct { } type Model struct { - confirmed bool config Config client api.Client header header.Model compact bool currentPage nomad.Page - previousPage nomad.Page pageModels map[nomad.Page]*page.Model inJobsMode bool @@ -171,7 +169,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case nomad.PageLoadedMsg: - dev.Debug(fmt.Sprintf("page loaded: %v", msg.Page)) if msg.Page == m.currentPage { m.getCurrentPageModel().SetHeader(msg.TableHeader) m.getCurrentPageModel().SetAllPageRows(msg.AllPageRows) @@ -188,8 +185,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // but returns empty results when one provides an empty token m.getCurrentPageModel().SetHeader([]string{"Error"}) m.getCurrentPageModel().SetAllPageRows([]page.Row{ - {"", "No results. Is the cluster empty or was no nomad token provided?"}, - {"", "Press q or ctrl+c to quit."}, + {Key: "", Row: "No results. Is the cluster empty or was no nomad token provided?"}, + {Key: "", Row: "Press q or ctrl+c to quit."}, }) m.getCurrentPageModel().SetViewportSelectionEnabled(false) } @@ -308,10 +305,24 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case nomad.TaskAdminActionCompleteMsg: m.getCurrentPageModel().SetToast( toast.New( - fmt.Sprintf("%s completed successfully", nomad.GetTaskAdminText(m.adminAction, msg.TaskName, msg.AllocName, msg.AllocID)), - ), + fmt.Sprintf( + "%s completed successfully", + nomad.GetTaskAdminText( + m.adminAction, msg.TaskName, msg.AllocName, msg.AllocID))), style.SuccessToast, ) + + case nomad.TaskAdminActionFailedMsg: + m.getCurrentPageModel().SetToast( + toast.New( + fmt.Sprintf( + "%s failed with error: %s", + nomad.GetTaskAdminText( + m.adminAction, msg.TaskName, msg.AllocName, msg.AllocID), + msg.Error())), + style.ErrorToast, + ) + } currentPageModel = m.getCurrentPageModel() @@ -409,7 +420,8 @@ func (m *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { m.adminAction = nomad.KeyToAdminAction(selectedPageRow.Key) case nomad.TaskAdminConfirmPage: if selectedPageRow.Key == constants.ConfirmationKey { - cmds = append(cmds, nomad.GetCmdForTaskAdminAction(m.client, m.adminAction, m.taskName, m.alloc.Name, m.alloc.ID)) + cmds = append(cmds, nomad.GetCmdForTaskAdminAction( + m.client, m.adminAction, m.taskName, m.alloc.Name, m.alloc.ID)) } else { backPage := m.currentPage.Backward(m.inJobsMode) m.setPage(backPage) @@ -609,7 +621,6 @@ func (m *Model) setPage(page nomad.Page) { m.getCurrentPageModel().HideToast() m.currentPage = page m.getCurrentPageModel().SetFilterPrefix(m.getFilterPrefix(page)) - if page.DoesLoad() { m.getCurrentPageModel().SetLoading(true) } else { @@ -691,17 +702,12 @@ func (m Model) getCurrentPageCmd() tea.Cmd { } allPageRows = append(allPageRows, page.Row{Row: formatter.StripOSCommandSequences(formatter.StripANSI(row))}) } - return nomad.PageLoadedMsg{ - Page: nomad.ExecCompletePage, - TableHeader: []string{"Exec Session Output"}, - AllPageRows: allPageRows, - } + return nomad.PageLoadedMsg{Page: nomad.ExecCompletePage, TableHeader: []string{"Exec Session Output"}, AllPageRows: allPageRows} } case nomad.AllocSpecPage: return nomad.FetchAllocSpec(m.client, m.alloc.ID) case nomad.LogsPage: - return nomad.FetchLogs( - m.client, m.alloc, m.taskName, m.logType, m.config.Log.Offset, m.config.Log.Tail) + return nomad.FetchLogs(m.client, m.alloc, m.taskName, m.logType, m.config.Log.Offset, m.config.Log.Tail) case nomad.LoglinePage: return nomad.PrettifyLine(m.logline, nomad.LoglinePage) case nomad.StatsPage: @@ -722,6 +728,7 @@ func (m Model) getCurrentPageCmd() tea.Cmd { AllPageRows: rows, } } + case nomad.TaskAdminConfirmPage: return func() tea.Msg { // this does no async work, just constructs the confirmation page @@ -736,6 +743,7 @@ func (m Model) getCurrentPageCmd() tea.Cmd { }, } } + default: panic(fmt.Sprintf("Load command for page:%s not found", m.currentPage)) } @@ -762,13 +770,5 @@ func (m Model) currentPageViewportSaving() bool { } func (m Model) getFilterPrefix(page nomad.Page) string { - return page.GetFilterPrefix( - m.config.Namespace, - m.jobID, - m.taskName, - m.alloc.Name, - m.alloc.ID, - m.config.Event.Topics, - m.config.Event.Namespace, - ) + return page.GetFilterPrefix(m.config.Namespace, m.jobID, m.taskName, m.alloc.Name, m.alloc.ID, m.config.Event.Topics, m.config.Event.Namespace) } diff --git a/internal/tui/components/page/page.go b/internal/tui/components/page/page.go index 1d4de002..3b95f0dd 100644 --- a/internal/tui/components/page/page.go +++ b/internal/tui/components/page/page.go @@ -399,12 +399,10 @@ func (m *Model) updateFilter() { m.filter.SetSuffix(" (no matches) ") } else if m.filter.Focused() { m.filter.SetSuffix( - fmt.Sprintf( - " (%d/%d, %s to apply) ", + fmt.Sprintf(" (%d/%d, %s to apply) ", m.pageData.FilteredSelectionNum+1, len(m.pageData.FilteredContentIdxs), - keymap.KeyMap.Forward.Help().Key, - ), + keymap.KeyMap.Forward.Help().Key), ) } else { m.filter.SetSuffix( diff --git a/internal/tui/nomad/pages.go b/internal/tui/nomad/pages.go index 56049e12..8af64802 100644 --- a/internal/tui/nomad/pages.go +++ b/internal/tui/nomad/pages.go @@ -251,7 +251,7 @@ func (p Page) String() string { case TaskAdminPage: return "task admin menu" case TaskAdminConfirmPage: - return "confirm task admin action" + return "confirm" } return "unknown" } @@ -415,6 +415,7 @@ type UpdatePageDataMsg struct { Page Page } +// Update page data with a delay. This is useful for pages that update. func UpdatePageDataWithDelay(id int, p Page, d time.Duration) tea.Cmd { if p.doesUpdate() && d > 0 { return tea.Tick(d, func(t time.Time) tea.Msg { return UpdatePageDataMsg{id, p} }) @@ -465,6 +466,11 @@ func GetPageKeyHelp( viewportKeyMap := viewport.GetKeyMap() secondRow := []key.Binding{viewportKeyMap.Save, keymap.KeyMap.Wrap} + + if currentPage.HasAdminMenu() { + secondRow = append(secondRow, keymap.KeyMap.AdminMenu) + } + thirdRow := []key.Binding{viewportKeyMap.Down, viewportKeyMap.Up, viewportKeyMap.PageDown, viewportKeyMap.PageUp, viewportKeyMap.Bottom, viewportKeyMap.Top} var fourthRow []key.Binding diff --git a/internal/tui/nomad/taskadmin.go b/internal/tui/nomad/taskadmin.go index d152fc61..3e1c203b 100644 --- a/internal/tui/nomad/taskadmin.go +++ b/internal/tui/nomad/taskadmin.go @@ -1,3 +1,6 @@ +/* Admin Actions for tasks +Restart, Stop, etc. +*/ package nomad import ( @@ -5,7 +8,6 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/hashicorp/nomad/api" "github.com/robinovitch61/wander/internal/tui/formatter" - "github.com/robinovitch61/wander/internal/tui/message" ) var ( @@ -20,11 +22,28 @@ type TaskAdminActionCompleteMsg struct { TaskName, AllocName, AllocID string } -func GetTaskAdminText(adminAction AdminAction, taskName, allocName, allocID string) string { - return fmt.Sprintf("%s task %s in %s (%s)", TaskAdminActions[adminAction], taskName, allocName, formatter.ShortAllocID(allocID)) +type TaskAdminActionFailedMsg struct { + Err error + TaskName, AllocName, AllocID string } -func GetCmdForTaskAdminAction(client api.Client, adminAction AdminAction, taskName, allocName, allocID string) tea.Cmd { +func (e TaskAdminActionFailedMsg) Error() string { return e.Err.Error() } + +func GetTaskAdminText( + adminAction AdminAction, taskName, allocName, allocID string) string { + return fmt.Sprintf( + "%s task %s in %s (%s)", + TaskAdminActions[adminAction], + taskName, allocName, formatter.ShortAllocID(allocID)) +} + +func GetCmdForTaskAdminAction( + client api.Client, + adminAction AdminAction, + taskName, + allocName, + allocID string, +) tea.Cmd { switch adminAction { case RestartTaskAction: return RestartTask(client, taskName, allocName, allocID) @@ -38,17 +57,21 @@ func GetCmdForTaskAdminAction(client api.Client, adminAction AdminAction, taskNa func RestartTask(client api.Client, taskName, allocName, allocID string) tea.Cmd { return func() tea.Msg { alloc, _, err := client.Allocations().Info(allocID, nil) + if err != nil { - // TODO LEO: we could return a TaskAdminActionFailedMsg here and display it in the toast - return message.ErrMsg{Err: err} + return TaskAdminActionFailedMsg{ + Err: err, + TaskName: taskName, AllocName: allocName, AllocID: allocID} } err = client.Allocations().Restart(alloc, taskName, nil) if err != nil { - // TODO LEO: we could return a TaskAdminActionFailedMsg here and display it in the toast - return message.ErrMsg{Err: err} + return TaskAdminActionFailedMsg{ + Err: err, + TaskName: taskName, AllocName: allocName, AllocID: allocID} } - return TaskAdminActionCompleteMsg{TaskName: taskName, AllocName: allocName, AllocID: allocID} + return TaskAdminActionCompleteMsg{ + TaskName: taskName, AllocName: allocName, AllocID: allocID} } } diff --git a/internal/tui/nomad/util.go b/internal/tui/nomad/util.go index 7a65fd9e..1b5d73e6 100644 --- a/internal/tui/nomad/util.go +++ b/internal/tui/nomad/util.go @@ -23,6 +23,7 @@ const ( StopJobAction ) +// AdminActionToKey and KeyToAdminAction are used to render the TaskAdminPage func AdminActionToKey(adminAction AdminAction) string { switch adminAction { case RestartTaskAction: diff --git a/internal/tui/style/style.go b/internal/tui/style/style.go index c48b5677..dbafb792 100644 --- a/internal/tui/style/style.go +++ b/internal/tui/style/style.go @@ -43,6 +43,4 @@ var ( StdErr = Regular.Copy().Foreground(red) SuccessToast = Bold.Copy().PaddingLeft(1).Foreground(black).Background(darkgreen) ErrorToast = Bold.Copy().PaddingLeft(1).Foreground(black).Background(darkred) - Red = Regular.Copy().Foreground(red) - Green = Regular.Copy().Foreground(darkgreen) )