Skip to content

Commit

Permalink
Implement Dashboard view of interactive workflow
Browse files Browse the repository at this point in the history
Signed-off-by: Anthony Emengo <aemengo@vmware.com>
  • Loading branch information
Anthony Emengo committed Nov 11, 2021
1 parent 26d8c5c commit d6a61b8
Show file tree
Hide file tree
Showing 9 changed files with 439 additions and 40 deletions.
5 changes: 5 additions & 0 deletions internal/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,11 @@ func (b *Builder) Order() dist.Order {
return b.order
}

// BaseImageName returns the name of the builder base image
func (b *Builder) BaseImageName() string {
return b.baseImageName
}

// Name returns the name of the builder
func (b *Builder) Name() string {
return b.image.Name()
Expand Down
6 changes: 6 additions & 0 deletions internal/builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,12 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, bldr.GID(), 4321)
})
})

when("#BaseImageName", func() {
it("return name of base image", func() {
h.AssertEq(t, bldr.BaseImageName(), "base/image")
})
})
})
})
}
Expand Down
143 changes: 143 additions & 0 deletions internal/termui/dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package termui

import (
"fmt"

"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"

"github.com/buildpacks/pack/pkg/dist"
)

type Dashboard struct {
app app
appTree *tview.TreeView
builderTree *tview.TreeView
planList *tview.List
logsView *tview.TextView
logs string
}

func NewDashboard(app app, appName string, bldr buildr, runImageName string, buildpackInfo []dist.BuildpackInfo) *Dashboard {
appTree, builderTree := initTrees(appName, bldr, runImageName)

planList, logsView := initDashboard(buildpackInfo)

d := &Dashboard{
app: app,
appTree: appTree,
builderTree: builderTree,
planList: planList,
logsView: logsView,
}

imagesView := tview.NewFlex().
SetDirection(tview.FlexRow).
AddItem(appTree, 0, 1, false).
AddItem(builderTree, 0, 1, true)

imagesView.
SetBorder(true).
SetTitleAlign(tview.AlignLeft).
SetTitle("| [::b]images[::-] |").
SetBackgroundColor(backgroundColor)

leftPane := tview.NewFlex().
SetDirection(tview.FlexRow).
AddItem(imagesView, 11, 0, false).
AddItem(planList, 0, 1, true)

screen := tview.NewFlex().
SetDirection(tview.FlexColumn).
AddItem(leftPane, 0, 1, true).
AddItem(logsView, 0, 1, false)

d.app.SetRoot(screen, true)
return d
}

func (d *Dashboard) Handle(txt string) {
d.app.QueueUpdateDraw(func() {
d.logs = d.logs + txt + "\n"
d.logsView.SetText(tview.TranslateANSI(d.logs))
})
}

func (d *Dashboard) Stop() {
// no-op
}

func initDashboard(buildpackInfos []dist.BuildpackInfo) (*tview.List, *tview.TextView) {
planList := tview.NewList()
planList.SetMainTextColor(tcell.ColorMediumTurquoise).
SetSelectedTextColor(tcell.ColorMediumTurquoise).
SetSelectedBackgroundColor(tcell.ColorDarkSlateGray).
SetSecondaryTextColor(tcell.ColorDimGray).
SetBorder(true).
SetBorderPadding(1, 1, 1, 1).
SetTitle("| [::b]plan[::-] |").
SetTitleAlign(tview.AlignLeft).
SetBackgroundColor(backgroundColor)

for _, buildpackInfo := range buildpackInfos {
planList.AddItem(
buildpackInfo.FullName(),
info(buildpackInfo),
'✔',
func() {},
)
}

logsView := tview.NewTextView()
logsView.SetDynamicColors(true).
SetTextAlign(tview.AlignLeft).
SetBorderPadding(1, 1, 3, 1).
SetTitleAlign(tview.AlignLeft).
SetBackgroundColor(backgroundColor)

return planList, logsView
}

func initTrees(appName string, bldr buildr, runImageName string) (*tview.TreeView, *tview.TreeView) {
var (
appImage = tview.NewTreeNode(fmt.Sprintf("app: [white::b]%s", appName)).SetColor(tcell.ColorDimGray)
appRunImage = tview.NewTreeNode(fmt.Sprintf(" run: [white::b]%s", runImageName)).SetColor(tcell.ColorDimGray)
builderImage = tview.NewTreeNode(fmt.Sprintf("builder: [white::b]%s", bldr.BaseImageName())).SetColor(tcell.ColorDimGray)
lifecycle = tview.NewTreeNode(fmt.Sprintf(" lifecycle: [white::b]%s", bldr.LifecycleDescriptor().Info.Version.String())).SetColor(tcell.ColorDimGray)
runImage = tview.NewTreeNode(fmt.Sprintf(" run: [white::b]%s", bldr.Stack().RunImage.Image)).SetColor(tcell.ColorDimGray)
buildpacks = tview.NewTreeNode(" [mediumturquoise::b]buildpacks")
)

appImage.AddChild(appRunImage)
builderImage.AddChild(lifecycle)
builderImage.AddChild(runImage)
builderImage.AddChild(buildpacks)

appTree := tview.NewTreeView()
appTree.
SetRoot(appImage).
SetGraphics(true).
SetGraphicsColor(tcell.ColorMediumTurquoise).
SetTitleAlign(tview.AlignLeft).
SetBorderPadding(1, 0, 4, 0).
SetBackgroundColor(backgroundColor)

builderTree := tview.NewTreeView()
builderTree.
SetRoot(builderImage).
SetGraphics(true).
SetGraphicsColor(tcell.ColorMediumTurquoise).
SetTitleAlign(tview.AlignLeft).
SetBorderPadding(0, 0, 4, 0).
SetBackgroundColor(backgroundColor)

return appTree, builderTree
}

func info(buildpackInfo dist.BuildpackInfo) string {
if buildpackInfo.Description != "" {
return buildpackInfo.Description
}

return buildpackInfo.Homepage
}
53 changes: 42 additions & 11 deletions internal/termui/detect.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
package termui

import (
"regexp"
"time"

"github.com/rivo/tview"

"github.com/buildpacks/pack/pkg/dist"
)

type Detect struct {
app app
textView *tview.TextView
doneChan chan bool
app app
bldr buildr

textView *tview.TextView
buildpackRegex *regexp.Regexp
buildpackChan chan dist.BuildpackInfo
doneChan chan bool
}

func NewDetect(app app) *Detect {
func NewDetect(app app, buildpackChan chan dist.BuildpackInfo, bldr buildr) *Detect {
d := &Detect{
app: app,
textView: detectStatusTV(app),
doneChan: make(chan bool, 1),
app: app,
textView: detectStatusTV(),
buildpackRegex: regexp.MustCompile(`^(\S+)\s+([\d\.]+)$`),
buildpackChan: buildpackChan,
doneChan: make(chan bool, 1),
bldr: bldr,
}

go d.start()
Expand All @@ -25,6 +35,13 @@ func NewDetect(app app) *Detect {
return d
}

func (d *Detect) Handle(txt string) {
m := d.buildpackRegex.FindStringSubmatch(txt)
if len(m) == 3 {
d.buildpackChan <- d.find(m[1], m[2])
}
}

func (d *Detect) Stop() {
d.doneChan <- true
}
Expand All @@ -45,24 +62,38 @@ func (d *Detect) start() {
for {
select {
case <-ticker.C:
d.textView.SetText(texts[i])
d.app.QueueUpdateDraw(func() {
d.textView.SetText(texts[i])
})

i++
if i == len(texts) {
i = 0
}
case <-d.doneChan:
ticker.Stop()
d.textView.SetText(doneText)

d.app.QueueUpdateDraw(func() {
d.textView.SetText(doneText)
})
return
}
}
}

func detectStatusTV(app app) *tview.TextView {
func (d *Detect) find(buildpackID, buildpackVersion string) dist.BuildpackInfo {
for _, buildpack := range d.bldr.Buildpacks() {
if buildpack.ID == buildpackID && buildpack.Version == buildpackVersion {
return buildpack
}
}

return dist.BuildpackInfo{}
}

func detectStatusTV() *tview.TextView {
tv := tview.NewTextView()
tv.SetBackgroundColor(backgroundColor)
tv.SetChangedFunc(func() { app.Draw() })
return tv
}

Expand Down
10 changes: 10 additions & 0 deletions internal/termui/fakes/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ func (a *App) Draw() *tview.Application {
return nil
}

func (a *App) QueueUpdateDraw(f func()) *tview.Application {
f()
a.DrawCallCount++
return nil
}

func (a *App) Run() error {
<-a.doneChan
return nil
Expand All @@ -35,3 +41,7 @@ func (a *App) Run() error {
func (a *App) StopRunning() {
a.doneChan <- true
}

func (a *App) ResetDrawCount() {
a.DrawCallCount = 0
}
38 changes: 38 additions & 0 deletions internal/termui/fakes/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package fakes

import (
"github.com/buildpacks/pack/internal/builder"
"github.com/buildpacks/pack/pkg/dist"
)

type Builder struct {
baseImageName string
buildpacks []dist.BuildpackInfo
lifecycleDescriptor builder.LifecycleDescriptor
stack builder.StackMetadata
}

func NewBuilder(baseImageName string, buildpacks []dist.BuildpackInfo, lifecycleDescriptor builder.LifecycleDescriptor, stack builder.StackMetadata) *Builder {
return &Builder{
baseImageName: baseImageName,
buildpacks: buildpacks,
lifecycleDescriptor: lifecycleDescriptor,
stack: stack,
}
}

func (b *Builder) BaseImageName() string {
return b.baseImageName
}

func (b *Builder) Buildpacks() []dist.BuildpackInfo {
return b.buildpacks
}

func (b *Builder) LifecycleDescriptor() builder.LifecycleDescriptor {
return b.lifecycleDescriptor
}

func (b *Builder) Stack() builder.StackMetadata {
return b.stack
}
Loading

0 comments on commit d6a61b8

Please sign in to comment.