feat(cli): add progress bar to instalation
This commit is contained in:
parent
e9914436c4
commit
3a97e96805
3 changed files with 109 additions and 59 deletions
1
go.mod
1
go.mod
|
|
@ -15,6 +15,7 @@ require (
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/catppuccin/go v0.2.0 // indirect
|
github.com/catppuccin/go v0.2.0 // indirect
|
||||||
|
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.4.5 // indirect
|
github.com/charmbracelet/x/ansi v0.4.5 // indirect
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -10,6 +10,8 @@ github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQW
|
||||||
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
|
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
|
||||||
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
|
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
|
||||||
github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM=
|
github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM=
|
||||||
|
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||||
|
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||||
github.com/charmbracelet/huh v0.6.0 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8=
|
github.com/charmbracelet/huh v0.6.0 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8=
|
||||||
github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU=
|
github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU=
|
||||||
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
|
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
|
||||||
|
|
|
||||||
165
main.go
165
main.go
|
|
@ -10,7 +10,8 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/list"
|
"github.com/charmbracelet/bubbles/progress"
|
||||||
|
"github.com/charmbracelet/bubbles/spinner"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/charmbracelet/huh"
|
"github.com/charmbracelet/huh"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
|
@ -76,7 +77,6 @@ func downloadGolang(golangVersion string) error {
|
||||||
} else {
|
} else {
|
||||||
link = "https://go.dev/dl/go" + golangVersion + ".linux-amd64.tar.gz"
|
link = "https://go.dev/dl/go" + golangVersion + ".linux-amd64.tar.gz"
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.Get(link)
|
resp, err := http.Get(link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Fehler beim Herunterladen von Go: %v", err)
|
return fmt.Errorf("Fehler beim Herunterladen von Go: %v", err)
|
||||||
|
|
@ -137,32 +137,120 @@ func installSpecialSoftware() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type model struct {
|
type model struct {
|
||||||
list list.Model
|
|
||||||
packages []string
|
packages []string
|
||||||
|
index int
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
spinner spinner.Model
|
||||||
|
progress progress.Model
|
||||||
|
done bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
currentPkgNameStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("211"))
|
||||||
|
doneStyle = lipgloss.NewStyle().Margin(1, 2)
|
||||||
|
checkMark = lipgloss.NewStyle().Foreground(lipgloss.Color("42")).SetString("✓")
|
||||||
|
)
|
||||||
|
|
||||||
|
func newModel(packages []string) model {
|
||||||
|
p := progress.New(
|
||||||
|
progress.WithDefaultGradient(),
|
||||||
|
progress.WithWidth(40),
|
||||||
|
progress.WithoutPercentage(),
|
||||||
|
)
|
||||||
|
s := spinner.New()
|
||||||
|
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("63"))
|
||||||
|
return model{
|
||||||
|
packages: packages,
|
||||||
|
spinner: s,
|
||||||
|
progress: p,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) Init() tea.Cmd {
|
func (m model) Init() tea.Cmd {
|
||||||
return nil
|
return tea.Batch(installPackageCmd(m.packages[m.index]), m.spinner.Tick)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
|
case tea.WindowSizeMsg:
|
||||||
|
m.width, m.height = msg.Width, msg.Height
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
if msg.String() == "q" {
|
switch msg.String() {
|
||||||
|
case "ctrl+c", "esc", "q":
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
}
|
}
|
||||||
case tea.WindowSizeMsg:
|
case installedPkgMsg:
|
||||||
h, v := lipgloss.NewStyle().Margin(1, 2).GetFrameSize()
|
pkg := m.packages[m.index]
|
||||||
m.list.SetSize(msg.Width-h, msg.Height-v)
|
if m.index >= len(m.packages)-1 {
|
||||||
}
|
m.done = true
|
||||||
|
return m, tea.Sequence(
|
||||||
|
tea.Printf("%s %s", checkMark, pkg),
|
||||||
|
tea.Quit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var cmd tea.Cmd
|
m.index++
|
||||||
m.list, cmd = m.list.Update(msg)
|
progressCmd := m.progress.SetPercent(float64(m.index) / float64(len(m.packages)))
|
||||||
return m, cmd
|
|
||||||
|
return m, tea.Batch(
|
||||||
|
progressCmd,
|
||||||
|
tea.Printf("%s %s", checkMark, pkg),
|
||||||
|
installPackageCmd(m.packages[m.index]),
|
||||||
|
)
|
||||||
|
case spinner.TickMsg:
|
||||||
|
var cmd tea.Cmd
|
||||||
|
m.spinner, cmd = m.spinner.Update(msg)
|
||||||
|
return m, cmd
|
||||||
|
case progress.FrameMsg:
|
||||||
|
newModel, cmd := m.progress.Update(msg)
|
||||||
|
if newModel, ok := newModel.(progress.Model); ok {
|
||||||
|
m.progress = newModel
|
||||||
|
}
|
||||||
|
return m, cmd
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m model) View() string {
|
func (m model) View() string {
|
||||||
return "\n" + m.list.View()
|
n := len(m.packages)
|
||||||
|
w := lipgloss.Width(fmt.Sprintf("%d", n))
|
||||||
|
|
||||||
|
if m.done {
|
||||||
|
return doneStyle.Render(fmt.Sprintf("Done! Installed %d packages.\n", n))
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgCount := fmt.Sprintf(" %*d/%*d", w, m.index, w, n)
|
||||||
|
|
||||||
|
spin := m.spinner.View() + " "
|
||||||
|
prog := m.progress.View()
|
||||||
|
cellsAvail := max(0, m.width-lipgloss.Width(spin+prog+pkgCount))
|
||||||
|
|
||||||
|
pkgName := currentPkgNameStyle.Render(m.packages[m.index])
|
||||||
|
info := lipgloss.NewStyle().MaxWidth(cellsAvail).Render("Installing " + pkgName)
|
||||||
|
|
||||||
|
cellsRemaining := max(0, m.width-lipgloss.Width(spin+info+prog+pkgCount))
|
||||||
|
gap := strings.Repeat(" ", cellsRemaining)
|
||||||
|
|
||||||
|
return spin + info + gap + prog + pkgCount
|
||||||
|
}
|
||||||
|
|
||||||
|
type installedPkgMsg string
|
||||||
|
|
||||||
|
func installPackageCmd(pkg string) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
if err := installPackage(installCommand, pkg); err != nil {
|
||||||
|
log.Printf("Fehler beim Installieren von %s: %v", pkg, err)
|
||||||
|
}
|
||||||
|
return installedPkgMsg(pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func max(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
|
|
@ -188,6 +276,8 @@ type Config struct {
|
||||||
SpecialPackages SpecialPackages `mapstructure:"special_packages"`
|
SpecialPackages SpecialPackages `mapstructure:"special_packages"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var installCommand string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cobra.OnInitialize(initConfig)
|
cobra.OnInitialize(initConfig)
|
||||||
rootCmd.PersistentFlags().StringP("config", "c", "", "Pfad zur Konfigurationsdatei")
|
rootCmd.PersistentFlags().StringP("config", "c", "", "Pfad zur Konfigurationsdatei")
|
||||||
|
|
@ -233,10 +323,6 @@ func installSpecialPackages(sp SpecialPackages) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type item string
|
|
||||||
|
|
||||||
func (i item) FilterValue() string { return string(i) }
|
|
||||||
|
|
||||||
func run(cmd *cobra.Command, args []string) {
|
func run(cmd *cobra.Command, args []string) {
|
||||||
os, err := getLinuxDistribution()
|
os, err := getLinuxDistribution()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -246,7 +332,7 @@ func run(cmd *cobra.Command, args []string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
installCommand, err := getInstallCommand(pm)
|
installCommand, err = getInstallCommand(pm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -274,51 +360,12 @@ func run(cmd *cobra.Command, args []string) {
|
||||||
packages = append(packages, cfg.Packages.NonHeadless...)
|
packages = append(packages, cfg.Packages.NonHeadless...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// packages := viper.GetStringSlice("packages")
|
m := newModel(packages)
|
||||||
|
|
||||||
items := make([]list.Item, len(packages))
|
|
||||||
for i, pkg := range packages {
|
|
||||||
items[i] = item(pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
m := model{
|
|
||||||
list: list.New(items, list.NewDefaultDelegate(), 0, 0),
|
|
||||||
packages: packages,
|
|
||||||
}
|
|
||||||
m.list.Title = "Zu installierende Pakete"
|
|
||||||
|
|
||||||
p := tea.NewProgram(m)
|
p := tea.NewProgram(m)
|
||||||
if _, err := p.Run(); err != nil {
|
if _, err := p.Run(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var confirm bool
|
|
||||||
form = huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewConfirm().
|
|
||||||
Title("Möchten Sie mit der Installation fortfahren?").
|
|
||||||
Value(&confirm),
|
|
||||||
),
|
|
||||||
).WithTheme(huh.ThemeCatppuccin())
|
|
||||||
|
|
||||||
err = form.Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !confirm {
|
|
||||||
fmt.Println("Installation abgebrochen.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pkg := range packages {
|
|
||||||
if err := installPackage(installCommand, pkg); err != nil {
|
|
||||||
log.Printf("Fehler beim Installieren von %s: %v", pkg, err)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("Erfolgreich installiert: %s\n", pkg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var installSpecial bool
|
var installSpecial bool
|
||||||
form = huh.NewForm(
|
form = huh.NewForm(
|
||||||
huh.NewGroup(
|
huh.NewGroup(
|
||||||
|
|
@ -342,10 +389,10 @@ func run(cmd *cobra.Command, args []string) {
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Installation der speziellen Pakete übersprungen")
|
fmt.Println("Installation der speziellen Pakete übersprungen")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := installSpecialPackages(cfg.SpecialPackages); err != nil {
|
if err := installSpecialPackages(cfg.SpecialPackages); err != nil {
|
||||||
log.Printf("Fehler bei der Installation spezieller Pakete: %v", err)
|
log.Printf("Fehler bei der Installation spezieller Pakete: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue