Skip to content

Commit

Permalink
feat: add commands to state resource page
Browse files Browse the repository at this point in the history
  • Loading branch information
leg100 committed Jun 17, 2024
1 parent 368d59e commit 9550973
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 63 deletions.
4 changes: 2 additions & 2 deletions internal/state/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ func (r *Resource) String() string {
return string(r.Address)
}

func newResource(ws resource.Resource, addr ResourceAddress, attrs json.RawMessage) (*Resource, error) {
func newResource(state resource.Resource, addr ResourceAddress, attrs json.RawMessage) (*Resource, error) {
res := &Resource{
Common: resource.New(resource.StateResource, ws),
Common: resource.New(resource.StateResource, state),
Address: addr,
}
if err := json.Unmarshal(attrs, &res.Attributes); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func newState(ws resource.Resource, r io.Reader) (*State, error) {
}
addr := ResourceAddress(b.String())
var err error
m[addr], err = newResource(ws, addr, instance.Attributes)
m[addr], err = newResource(state, addr, instance.Attributes)
if err != nil {
return nil, fmt.Errorf("decoding resource %s: %w", addr, err)
}
Expand Down
20 changes: 20 additions & 0 deletions internal/task/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,26 @@ func (g *Group) Finished() int {
return finished
}

func (g *Group) Exited() int {
var exited int
for _, t := range g.Tasks {
if t.State == Exited {
exited++
}
}
return exited
}

func (g *Group) Errored() int {
var errored int
for _, t := range g.Tasks {
if t.State == Errored {
errored++
}
}
return errored
}

func SortGroupsByCreated(i, j *Group) int {
if i.Created.After(j.Created) {
return -1
Expand Down
3 changes: 2 additions & 1 deletion internal/tui/color.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ var (
Light: string(Grey),
}

RunReportBackgroundColor = EvenLighterGrey
RunReportBackgroundColor = EvenLighterGrey
GroupReportBackgroundColor = EvenLighterGrey

ScrollPercentageBackground = lipgloss.AdaptiveColor{
Dark: string(DarkGrey),
Expand Down
86 changes: 64 additions & 22 deletions internal/tui/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strconv"
"strings"

"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/leg100/pug/internal/logging"
Expand Down Expand Up @@ -217,29 +218,27 @@ func (h *Helpers) RunReport(report run.Report, table bool) string {
return s
}

func Breadcrumbs(title string, res resource.Resource, crumbs ...string) string {
// format: title{task command}[workspace name](module path)
switch res := res.(type) {
case *task.Task:
cmd := TitleCommand.Render(res.String())
return Breadcrumbs(title, res.GetParent(), cmd)
case *state.Resource:
addr := TitleAddress.Render(res.String())
return Breadcrumbs(title, res.GetParent(), addr)
case *task.Group:
cmd := TitleCommand.Render(res.String())
id := TitleID.Render(res.GetID().String())
return Breadcrumbs(title, res.GetParent(), cmd, id)
case *run.Run:
// Skip run info in breadcrumbs
return Breadcrumbs(title, res.GetParent(), crumbs...)
case *workspace.Workspace:
name := TitleWorkspace.Render(res.String())
return Breadcrumbs(title, res.GetParent(), append(crumbs, name)...)
case *module.Module:
crumbs = append(crumbs, TitlePath.Render(res.String()))
// RunReport renders a colored summary of a run's changes. Set table to true if
// the report is rendered within a table row.
func (h *Helpers) GroupReport(group *task.Group, table bool) string {
var background lipgloss.TerminalColor = lipgloss.NoColor{}
if !table {
background = GroupReportBackgroundColor
}
return fmt.Sprintf("%s%s", Title.Render(title), strings.Join(crumbs, ""))
slash := Regular.Copy().Background(background).Foreground(Black).Render("/")
exited := Regular.Copy().Background(background).Foreground(Green).Render(fmt.Sprintf("%d", group.Exited()))
total := Regular.Copy().Background(background).Foreground(Black).Render(fmt.Sprintf("%d", len(group.Tasks)))

s := fmt.Sprintf("%s%s%s", exited, slash, total)
if errored := group.Errored(); errored > 0 {
erroredString := Regular.Copy().Background(background).Foreground(Red).Render(fmt.Sprintf("%d", errored))
s = fmt.Sprintf("%s%s%s", erroredString, slash, s)
}

if !table {
s = Padded.Background(background).Render(s)
}
return s
}

func (h *Helpers) CreateTasks(cmd string, fn task.Func, ids ...resource.ID) tea.Cmd {
Expand All @@ -262,3 +261,46 @@ func (h *Helpers) CreateTasks(cmd string, fn task.Func, ids ...resource.ID) tea.
}
}
}

func (h *Helpers) Move(workspaceID resource.ID, from state.ResourceAddress) tea.Cmd {
return CmdHandler(PromptMsg{
Prompt: "Enter destination address: ",
InitialValue: string(from),
Action: func(v string) tea.Cmd {
if v == "" {
return nil
}
fn := func(workspaceID resource.ID) (*task.Task, error) {
return h.StateService.Move(workspaceID, from, state.ResourceAddress(v))
}
return h.CreateTasks("state-mv", fn, workspaceID)
},
Key: key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "confirm")),
Cancel: key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "cancel")),
})
}

func Breadcrumbs(title string, res resource.Resource, crumbs ...string) string {
// format: title{task command}[workspace name](module path)
switch res := res.(type) {
case *task.Task:
cmd := TitleCommand.Render(res.String())
return Breadcrumbs(title, res.GetParent(), cmd)
case *state.Resource:
addr := TitleAddress.Render(res.String())
return Breadcrumbs(title, res.GetParent().GetParent(), addr)
case *task.Group:
cmd := TitleCommand.Render(res.String())
id := TitleID.Render(res.GetID().String())
return Breadcrumbs(title, res.GetParent(), cmd, id)
case *run.Run:
// Skip run info in breadcrumbs
return Breadcrumbs(title, res.GetParent(), crumbs...)
case *workspace.Workspace:
name := TitleWorkspace.Render(res.String())
return Breadcrumbs(title, res.GetParent(), append(crumbs, name)...)
case *module.Module:
crumbs = append(crumbs, TitlePath.Render(res.String()))
}
return fmt.Sprintf("%s%s", Title.Render(title), strings.Join(crumbs, ""))
}
6 changes: 2 additions & 4 deletions internal/tui/logs/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}

func (m model) Title() string {
title := tui.Title.Render("LogMessage")
id := tui.Regular.Copy().Foreground(tui.Pink).Render(fmt.Sprintf("#%d", m.msg.Serial))
s := fmt.Sprintf("%s(%s)", title, id)
return tui.Bold.Render(s)
serial := tui.TitleSerial.Render(fmt.Sprintf("#%d", m.msg.Serial))
return tui.Breadcrumbs("LogMessage", resource.GlobalResource, serial)
}

func (m model) View() string {
Expand Down
1 change: 1 addition & 0 deletions internal/tui/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var (
TitleID = Padded.Copy().Foreground(White).Background(Green)
TitleAddress = Padded.Copy().Foreground(White).Background(Blue)
TitleSerial = Padded.Copy().Foreground(White).Background(Orange)
TitleTainted = Padded.Copy().Foreground(White).Background(Red)

RunReportStyle = Padded.Copy().Background(EvenLighterGrey)
)
6 changes: 3 additions & 3 deletions internal/tui/task/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ func (mm *GroupMaker) Make(parent resource.Resource, width, height int) (tea.Mod
return nil, errors.New("expected taskgroup resource")
}

progress := progress.New(progress.WithDefaultGradient())
progress := progress.New(progress.WithDefaultGradient(), progress.WithScaledGradient("253", "#606362"))
progress.Width = 20
progress.ShowPercentage = false
progress.EmptyColor = "0"

list, err := mm.taskListMaker.Make(parent, width, height)
if err != nil {
Expand Down Expand Up @@ -130,8 +131,7 @@ func (m groupModel) Title() string {
}

func (m groupModel) Status() string {
pbar := m.progress.View()
return fmt.Sprintf("%s %d/%d", pbar, m.group.Finished(), len(m.group.Tasks))
return m.helpers.GroupReport(m.group, false)
}

func (m *groupModel) handleTasks(tasks ...*task.Task) (tea.Cmd, bool) {
Expand Down
5 changes: 2 additions & 3 deletions internal/tui/task/group_list.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package task

import (
"fmt"
"time"

"github.com/charmbracelet/bubbles/key"
Expand All @@ -17,7 +16,7 @@ var (
taskGroupCount = table.Column{
Key: "tasks",
Title: "TASKS",
Width: len("TASKS"),
Width: 10,
}
taskGroupID = table.Column{
Key: "task_group_id",
Expand All @@ -43,7 +42,7 @@ func (m *GroupListMaker) Make(_ resource.Resource, width, height int) (tea.Model
row := table.RenderedRow{
commandColumn.Key: g.Command,
taskGroupID.Key: g.ID.String(),
taskGroupCount.Key: fmt.Sprintf("%d/%d", g.Finished(), len(g.Tasks)),
taskGroupCount.Key: m.Helpers.GroupReport(g, true),
ageColumn.Key: tui.Ago(time.Now(), g.Created),
}
return row
Expand Down
71 changes: 61 additions & 10 deletions internal/tui/workspace/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ import (
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/leg100/pug/internal/resource"
"github.com/leg100/pug/internal/run"
"github.com/leg100/pug/internal/state"
"github.com/leg100/pug/internal/task"
"github.com/leg100/pug/internal/tui"
"github.com/leg100/pug/internal/tui/keys"
)

type ResourceMaker struct {
Helpers *tui.Helpers
StateService tui.StateService
RunService tui.RunService
Helpers *tui.Helpers

disableBorders bool
}
Expand All @@ -25,9 +29,10 @@ func (mm *ResourceMaker) Make(res resource.Resource, width, height int) (tea.Mod
}

m := resourceModel{
helpers: mm.Helpers,
resource: stateResource,
border: !mm.disableBorders,
StateService: mm.StateService,
helpers: mm.Helpers,
resource: stateResource,
border: !mm.disableBorders,
}

marshaled, err := json.MarshalIndent(stateResource.Attributes, "", "\t")
Expand All @@ -45,6 +50,9 @@ func (mm *ResourceMaker) Make(res resource.Resource, width, height int) (tea.Mod
}

type resourceModel struct {
StateService tui.StateService
RunService tui.RunService

viewport tui.Viewport
resource *state.Resource
helpers *tui.Helpers
Expand All @@ -57,11 +65,46 @@ func (m resourceModel) Init() tea.Cmd {

func (m resourceModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
cmd tea.Cmd
cmds []tea.Cmd
createRunOptions run.CreateOptions
)

switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, resourcesKeys.Taint):
fn := func(workspaceID resource.ID) (*task.Task, error) {
return m.StateService.Taint(workspaceID, m.resource.Address)
}
return m, m.helpers.CreateTasks("taint", fn, m.resource.Workspace().GetID())
case key.Matches(msg, resourcesKeys.Untaint):
fn := func(workspaceID resource.ID) (*task.Task, error) {
return m.StateService.Untaint(workspaceID, m.resource.Address)
}
return m, m.helpers.CreateTasks("untaint", fn, m.resource.Workspace().GetID())
case key.Matches(msg, resourcesKeys.Move):
return m, m.helpers.Move(m.resource.Workspace().GetID(), m.resource.Address)
case key.Matches(msg, keys.Common.Delete):
fn := func(workspaceID resource.ID) (*task.Task, error) {
return m.StateService.Delete(workspaceID, m.resource.Address)
}
return m, tui.YesNoPrompt(
"Delete resource?",
m.helpers.CreateTasks("state-rm", fn, m.resource.Workspace().GetID()),
)
case key.Matches(msg, keys.Common.Destroy):
// Create a targeted destroy plan.
createRunOptions.Destroy = true
fallthrough
case key.Matches(msg, keys.Common.Plan):
// Create a targeted plan.
createRunOptions.TargetAddrs = []state.ResourceAddress{m.resource.Address}
fn := func(workspaceID resource.ID) (*task.Task, error) {
return m.RunService.Plan(workspaceID, createRunOptions)
}
return m, m.helpers.CreateTasks("plan", fn, m.resource.Workspace().GetID())
}
case tea.WindowSizeMsg:
m.viewport.SetDimensions(m.viewportWidth(msg.Width), m.viewportHeight(msg.Height))
return m, nil
Expand Down Expand Up @@ -96,12 +139,20 @@ func (m resourceModel) viewportHeight(height int) int {
}

func (m resourceModel) Title() string {
return tui.Breadcrumbs("Resource", m.resource)
var tainted string
if m.resource.Tainted {
tainted = tui.TitleTainted.Render("tainted")
}
return tui.Breadcrumbs("Resource", m.resource) + tainted
}

func (m resourceModel) HelpBindings() []key.Binding {
bindings := []key.Binding{
keys.Common.Cancel,
return []key.Binding{
keys.Common.Plan,
keys.Common.Destroy,
keys.Common.Delete,
resourcesKeys.Move,
resourcesKeys.Taint,
resourcesKeys.Untaint,
}
return bindings
}
Loading

0 comments on commit 9550973

Please sign in to comment.