From b11a1926257992e7388b6c87e599a5d5f98cd448 Mon Sep 17 00:00:00 2001 From: unclegedd Date: Sun, 10 Mar 2024 16:49:32 -0500 Subject: [PATCH] WIP: multiple progress bars --- src/pkg/bundle/deploy.go | 6 +- src/pkg/bundle/tui/tui.go | 185 ++++++++++++++++++++----------------- src/pkg/sources/remote.go | 1 + src/pkg/sources/tarball.go | 1 + 4 files changed, 103 insertions(+), 90 deletions(-) diff --git a/src/pkg/bundle/deploy.go b/src/pkg/bundle/deploy.go index 3c5edd777..597ecadee 100644 --- a/src/pkg/bundle/deploy.go +++ b/src/pkg/bundle/deploy.go @@ -122,7 +122,7 @@ func deployPackages(packages []types.Package, resume bool, b *Bundle, zarfPackag } // deploy each package - for _, pkg := range packagesToDeploy { + for i, pkg := range packagesToDeploy { sha := strings.Split(pkg.Ref, "@sha256:")[1] // using appended SHA from create! pkgTmp, err := utils.MakeTempDir(config.CommonOptions.TempDirectory) if err != nil { @@ -178,9 +178,9 @@ func deployPackages(packages []types.Package, resume bool, b *Bundle, zarfPackag return err } - // bubbletea recommends calling the Program directly; calling model.Update() doesn't work - // https://github.com/charmbracelet/bubbletea/discussions/374 + // bubbletea recommends calling the Program directly: https://github.com/charmbracelet/bubbletea/discussions/374 tui.Program.Send(fmt.Sprintf("package:%s", pkg.Name)) + tui.Program.Send(fmt.Sprintf("idx:%d", i)) if err := pkgClient.Deploy(); err != nil { return err } diff --git a/src/pkg/bundle/tui/tui.go b/src/pkg/bundle/tui/tui.go index 7438433d8..3f819473b 100644 --- a/src/pkg/bundle/tui/tui.go +++ b/src/pkg/bundle/tui/tui.go @@ -20,7 +20,7 @@ import ( "github.com/defenseunicorns/zarf/src/types" ) -// todo: watch naming collisions, spinner also has a TickMsg +// todo: watch naming collisions, spinners also has a TickMsg type tickMsg time.Time type operation string @@ -40,45 +40,36 @@ type bndlClientShim interface { } type model struct { - bndlClient bndlClientShim - completeChan chan int - fatalChan chan error - progress progress.Model - currentPkg string - totalComponents int - componentChan chan int - spinner spinner.Model - confirmed bool - complete bool + bndlClient bndlClientShim + completeChan chan int + fatalChan chan error + progressBars []progress.Model + pkgNames []string + pkgIdx int + totalComponentsPerPkg []int + spinners []spinner.Model + confirmed bool + complete bool } func InitModel(client bndlClientShim) model { - // configure spinner - s := spinner.New() - s.Spinner = spinner.Dot - s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) - var confirmed bool if config.CommonOptions.Confirm { confirmed = true } return model{ - bndlClient: client, - completeChan: make(chan int), - fatalChan: make(chan error), - componentChan: make(chan int), - progress: progress.New(progress.WithDefaultGradient()), - currentPkg: "", - spinner: s, - confirmed: confirmed, + bndlClient: client, + completeChan: make(chan int), + fatalChan: make(chan error), + confirmed: confirmed, } } func (m model) Init() tea.Cmd { return tea.Sequence(func() tea.Msg { return DeployOp - }, m.spinner.Tick) + }) } // allows us to get way more Zarf output @@ -128,53 +119,59 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.complete = true return m, tea.Sequence(tickCmd()) case <-m.fatalChan: - m.spinner.Spinner.Frames = []string{""} - m.spinner.Style = lipgloss.NewStyle().SetString("❌") - s, spinnerCmd := m.spinner.Update(spinner.TickMsg{}) - m.spinner = s + m.spinners[m.pkgIdx].Spinner.Frames = []string{""} + m.spinners[m.pkgIdx].Style = lipgloss.NewStyle().SetString("❌") + s, spinnerCmd := m.spinners[m.pkgIdx].Update(spinner.TickMsg{}) + m.spinners[m.pkgIdx] = s return m, tea.Sequence(spinnerCmd, finalPause(), tea.Quit) default: switch msg := msg.(type) { case progress.FrameMsg: - progressModel, cmd := m.progress.Update(msg) - m.progress = progressModel.(progress.Model) + progressModel, cmd := m.progressBars[m.pkgIdx].Update(msg) + m.progressBars[m.pkgIdx] = progressModel.(progress.Model) return m, cmd case tickMsg: var progressCmd tea.Cmd if m.complete { - progressCmd = m.progress.SetPercent(100) - m.spinner.Spinner.Frames = []string{""} - m.spinner.Style = lipgloss.NewStyle().SetString("✅") - s, spinnerCmd := m.spinner.Update(spinner.TickMsg{}) - m.spinner = s + progressCmd = m.progressBars[m.pkgIdx].SetPercent(100) + m.spinners[m.pkgIdx].Spinner.Frames = []string{""} + m.spinners[m.pkgIdx].Style = lipgloss.NewStyle().SetString("✅") + s, spinnerCmd := m.spinners[m.pkgIdx].Update(spinner.TickMsg{}) + m.spinners[m.pkgIdx] = s return m, tea.Sequence(progressCmd, spinnerCmd, finalPause(), tea.Quit) } - if m.totalComponents > 0 { - deployedPkg := GetDeployedPackage(m.currentPkg) + if len(m.totalComponentsPerPkg) > m.pkgIdx && m.totalComponentsPerPkg[m.pkgIdx] > 0 { + deployedPkg := GetDeployedPackage(m.pkgNames[m.pkgIdx]) if deployedPkg != nil && !resetProgress { // todo: instead of going off of DeployedComponents, find a way to include deployedPkg.DeployedComponents[0].Status - progressCmd = m.progress.SetPercent(float64(len(deployedPkg.DeployedComponents)) / float64(m.totalComponents)) - if m.progress.Percent() == 1 { + progressCmd = m.progressBars[m.pkgIdx].SetPercent( + float64(len(deployedPkg.DeployedComponents)) / float64(m.totalComponentsPerPkg[m.pkgIdx]), + ) + if m.progressBars[m.pkgIdx].Percent() == 1 { // todo: instead of going off percentage, go off successful deployment of the pkg - // stop the spinner and show success - m.spinner.Spinner.Frames = []string{""} - m.spinner.Style = lipgloss.NewStyle().SetString("✅") + // stop the spinners and show success + m.spinners[m.pkgIdx].Spinner.Frames = []string{""} + m.spinners[m.pkgIdx].Style = lipgloss.NewStyle().SetString("✅") } } else { // handle upgrade scenario by resetting the progress bar until DeployedComponents is back to 1 (ie. the first component) - progressCmd = m.progress.SetPercent(0) + progressCmd = m.progressBars[m.pkgIdx].SetPercent(0) if deployedPkg != nil && len(deployedPkg.DeployedComponents) >= 1 { resetProgress = false } } } - // must send a spinner.TickMsg to the spinner to keep it spinning - s, spinnerCmd := m.spinner.Update(spinner.TickMsg{}) - m.spinner = s + // must send a spinners.TickMsg to the spinners to keep it spinning + if len(m.spinners) > 0 { + s, spinnerCmd := m.spinners[m.pkgIdx].Update(spinner.TickMsg{}) + m.spinners[m.pkgIdx] = s + + return m, tea.Sequence(progressCmd, spinnerCmd, tickCmd()) + } - return m, tea.Sequence(progressCmd, spinnerCmd, tickCmd()) + return m, tea.Sequence(progressCmd, tickCmd()) case tea.KeyMsg: switch msg.String() { @@ -209,14 +206,23 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case string: if strings.Split(msg, ":")[0] == "package" { pkgName := strings.Split(msg, ":")[1] - m.currentPkg = pkgName + m.pkgNames = append(m.pkgNames, pkgName) // if pkg is already deployed, set resetProgress to true if deployedPkg := GetDeployedPackage(pkgName); deployedPkg != nil && len(deployedPkg.DeployedComponents) != 0 { resetProgress = true } } else if strings.Split(msg, ":")[0] == "totalComponents" { if totalComponents, err := strconv.Atoi(strings.Split(msg, ":")[1]); err == nil { - m.totalComponents = totalComponents // else return err + m.totalComponentsPerPkg = append(m.totalComponentsPerPkg, totalComponents) + } + } else if strings.Split(msg, ":")[0] == "idx" { + if currentPkgIdx, err := strconv.Atoi(strings.Split(msg, ":")[1]); err == nil { + m.pkgIdx = currentPkgIdx + m.progressBars = append(m.progressBars, progress.New(progress.WithDefaultGradient())) + s := spinner.New() + s.Spinner = spinner.Dot + s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) + m.spinners = append(m.spinners, s) } } } @@ -235,48 +241,53 @@ func (m model) View() string { } func (m model) deployView() string { - width := 100 // todo: make dynamic? - text := lipgloss.NewStyle(). - Width(50). - Align(lipgloss.Left). - Padding(0, 3). - Render(fmt.Sprintf("%s Package %s deploying ...", m.spinner.View(), m.currentPkg)) - - // render progress bar until deployment is complete - progressBar := lipgloss.NewStyle(). - Width(50). - Align(lipgloss.Left). - Padding(0, 3). - MarginTop(1). - Render(m.progress.View()) - ui := lipgloss.JoinVertical(lipgloss.Center, text, progressBar) - - if m.complete { - text = lipgloss.NewStyle(). + view := "" + for i := range m.progressBars { + width := 100 // todo: make dynamic? + text := lipgloss.NewStyle(). Width(50). Align(lipgloss.Left). Padding(0, 3). - Render(fmt.Sprintf("%s Package %s deployed", m.spinner.View(), m.currentPkg)) - ui = lipgloss.JoinVertical(lipgloss.Center, text) - } + Render(fmt.Sprintf("%s Package %s deploying ...", m.spinners[i].View(), m.pkgNames[i])) - boxStyle := lipgloss.NewStyle(). - Border(lipgloss.RoundedBorder()). - BorderForeground(lipgloss.Color("#874BFD")). - Padding(1, 0). - BorderTop(true). - BorderLeft(true). - BorderRight(true). - BorderBottom(true) - subtle := lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"} - - box := lipgloss.Place(width, 9, - lipgloss.Left, lipgloss.Top, - boxStyle.Render(ui), - lipgloss.WithWhitespaceForeground(subtle), - ) + // render progress bar until deployment is complete + bar := lipgloss.NewStyle(). + Width(50). + Align(lipgloss.Left). + Padding(0, 3). + MarginTop(1). + Render(m.progressBars[i].View()) + + ui := lipgloss.JoinVertical(lipgloss.Center, text, bar) + + if m.complete { + text = lipgloss.NewStyle(). + Width(50). + Align(lipgloss.Left). + Padding(0, 3). + Render(fmt.Sprintf("%s Package %s deployed", m.spinners[i].View(), m.pkgNames[i])) + ui = lipgloss.JoinVertical(lipgloss.Center, text) + } + + boxStyle := lipgloss.NewStyle(). + Border(lipgloss.RoundedBorder()). + BorderForeground(lipgloss.Color("#874BFD")). + Padding(1, 0). + BorderTop(true). + BorderLeft(true). + BorderRight(true). + BorderBottom(true) + subtle := lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"} + + box := lipgloss.Place(width, 9, + lipgloss.Left, lipgloss.Top, + boxStyle.Render(ui), + lipgloss.WithWhitespaceForeground(subtle), + ) + view += fmt.Sprintf("%s\n", box) + } - return box + return view } func (m model) preDeployView() string { diff --git a/src/pkg/sources/remote.go b/src/pkg/sources/remote.go index ac76d591d..aa46a66a6 100644 --- a/src/pkg/sources/remote.go +++ b/src/pkg/sources/remote.go @@ -51,6 +51,7 @@ func (r *RemoteBundle) LoadPackage(dst *layout.PackagePaths, unarchiveAll bool) } // record number of components to be deployed for TUI + // todo: won't work for optional components...... tui.Program.Send(fmt.Sprintf("totalComponents:%d", len(pkg.Components))) dst.SetFromLayers(layers) diff --git a/src/pkg/sources/tarball.go b/src/pkg/sources/tarball.go index 6413db91c..709df35ea 100644 --- a/src/pkg/sources/tarball.go +++ b/src/pkg/sources/tarball.go @@ -54,6 +54,7 @@ func (t *TarballBundle) LoadPackage(dst *layout.PackagePaths, unarchiveAll bool) dst.SetFromPaths(files) // record number of components to be deployed for TUI + // todo: won't work for optional components...... tui.Program.Send(fmt.Sprintf("totalComponents:%d", len(pkg.Components))) if err := sources.ValidatePackageIntegrity(dst, pkg.Metadata.AggregateChecksum, t.isPartial); err != nil {