Skip to content

Commit

Permalink
feat: add plugins management (#16)
Browse files Browse the repository at this point in the history
* feat: add plugins management

* fix: remove code duplication

* fix: empty input field after install plugin
  • Loading branch information
pidanou authored Dec 21, 2024
1 parent 7a30469 commit 5886d87
Show file tree
Hide file tree
Showing 18 changed files with 329 additions and 119 deletions.
39 changes: 39 additions & 0 deletions components/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package components
import (
"github.com/charmbracelet/bubbles/table"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/pidanou/helmtui/styles"
)

type ColumnDefinition struct {
Expand Down Expand Up @@ -50,3 +52,40 @@ func SetTable(t *table.Model, cols []ColumnDefinition, targetWidth int) tea.Cmd
t.SetWidth(targetWidth)
return nil
}

func GenerateTable() table.Model {
t := table.New()
s := table.DefaultStyles()
k := table.DefaultKeyMap()
k.HalfPageUp.Unbind()
k.PageDown.Unbind()
k.HalfPageDown.Unbind()
k.HalfPageDown.Unbind()
k.GotoBottom.Unbind()
k.GotoTop.Unbind()
s.Header = s.Header.
BorderStyle(styles.Border).
BorderForeground(lipgloss.Color("240")).
BorderBottom(true).
Bold(true)
s.Selected = s.Selected.
Foreground(lipgloss.Color("229")).
Background(lipgloss.Color("57")).
Bold(false)

t.SetStyles(s)
t.KeyMap = k
return t
}

func RenderTable(t table.Model, height int, width int) string {
var topBorder string
t.SetHeight(height)
t.SetWidth(width)
view := t.View()
var baseStyle lipgloss.Style
topBorder = styles.GenerateTopBorderWithTitle(" Releases ", t.Width(), styles.Border, styles.InactiveStyle)
baseStyle = styles.InactiveStyle.Border(styles.Border, false, true, true)
view = baseStyle.Render(view)
return lipgloss.JoinVertical(lipgloss.Left, topBorder, view)
}
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ module github.com/pidanou/helmtui

go 1.22.3

require github.com/charmbracelet/bubbletea v1.2.4
require (
github.com/charmbracelet/bubbletea v1.2.4
github.com/stretchr/testify v1.10.0
)

require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
24 changes: 2 additions & 22 deletions hub/hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import (
"github.com/charmbracelet/bubbles/textinput"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/pidanou/helmtui/components"
"github.com/pidanou/helmtui/styles"
"github.com/pidanou/helmtui/types"
)

Expand Down Expand Up @@ -38,35 +36,17 @@ const (
)

func InitModel() tea.Model {
s := table.DefaultStyles()
k := table.DefaultKeyMap()
k.HalfPageUp.Unbind()
k.PageDown.Unbind()
k.HalfPageDown.Unbind()
k.HalfPageDown.Unbind()
k.GotoBottom.Unbind()
k.GotoTop.Unbind()
s.Header = s.Header.
BorderStyle(styles.Border).
BorderForeground(lipgloss.Color("240")).
BorderBottom(true).
Bold(true)
s.Selected = s.Selected.
Foreground(lipgloss.Color("229")).
Background(lipgloss.Color("57")).
Bold(false)
resultTable := components.GenerateTable()
m := HubModel{
searchBar: textinput.New(),
resultTable: table.New(),
resultTable: resultTable,
defaultValueVP: viewport.New(0, 0),
help: help.New(),
view: searchView,
repoAddInput: textinput.New(),
}
m.searchBar.Placeholder = "/ to Search a package"
m.repoAddInput.Placeholder = "Enter local repository name"
m.resultTable.SetStyles(s)
m.resultTable.KeyMap = k
return m
}

Expand Down
83 changes: 83 additions & 0 deletions plugins/overview.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package plugins

import (
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/table"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/pidanou/helmtui/components"
"github.com/pidanou/helmtui/types"
)

var pluginsCols = []components.ColumnDefinition{
{Title: "Name", FlexFactor: 1},
{Title: "Version", FlexFactor: 1},
{Title: "description", FlexFactor: 3},
}

type PluginsModel struct {
pluginsTable table.Model
installPluginInput textinput.Model
help help.Model
keys keyMap
width int
height int
}

func InitModel() PluginsModel {
table := components.GenerateTable()
input := textinput.New()
input.Placeholder = "Enter plugin path/url"
return PluginsModel{pluginsTable: table, help: help.New(), keys: overviewKeys, installPluginInput: input}
}

func (m PluginsModel) Init() tea.Cmd {
return m.list
}

func (m PluginsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
var cmds []tea.Cmd

switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
components.SetTable(&m.pluginsTable, pluginsCols, m.width)
case types.PluginsListMsg:
m.pluginsTable.SetRows(msg.Content)
case types.PluginInstallMsg:
m.installPluginInput.Blur()
m.installPluginInput.SetValue("")
return m, m.list
case types.PluginUninstallMsg:
return m, m.list
case tea.KeyMsg:
switch {
case key.Matches(msg, m.keys.Install):
cmds = append(cmds, m.installPluginInput.Focus())
return m, tea.Batch(cmds...)
case key.Matches(msg, m.keys.Uninstall):
if !m.installPluginInput.Focused() {
return m, m.uninstall
}
case key.Matches(msg, m.keys.Update):
if !m.installPluginInput.Focused() {
return m, m.update
}
case key.Matches(msg, m.keys.Cancel):
m.installPluginInput.Blur()
return m, tea.Batch(cmds...)
case key.Matches(msg, m.keys.Refresh):
return m, m.list
case msg.String() == "enter":
if m.installPluginInput.Focused() {
return m, m.install
}
}
}
m.installPluginInput, cmd = m.installPluginInput.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
78 changes: 78 additions & 0 deletions plugins/overview_commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package plugins

import (
"bytes"
"errors"
"os/exec"
"strings"

"github.com/charmbracelet/bubbles/table"
tea "github.com/charmbracelet/bubbletea"
"github.com/pidanou/helmtui/types"
)

func (m PluginsModel) list() tea.Msg {
var stdout bytes.Buffer
var rows = []table.Row{}

// Create the command
cmd := exec.Command("helm", "plugin", "ls")
cmd.Stdout = &stdout
err := cmd.Run()
if err != nil {
return types.ListReleasesMsg{Err: err}
}

lines := strings.Split(stdout.String(), "\n")
lines = lines[1 : len(lines)-1]

for _, line := range lines {
fields := strings.Fields(line)
name := fields[0]
version := fields[1]
description := strings.Join(fields[2:], " ")
row := []string{name, version, description}
rows = append(rows, row)
}
return types.PluginsListMsg{Content: rows}
}

func (m PluginsModel) install() tea.Msg {
pluginName := m.installPluginInput.Value()
if pluginName == "" {
return types.PluginInstallMsg{Err: errors.New("No plugin")}
}
cmd := exec.Command("helm", "plugin", "install", strings.TrimSpace(pluginName))
err := cmd.Run()
if err != nil {
return types.PluginInstallMsg{Err: errors.New("Cannot install plugin")}
}
return types.PluginInstallMsg{Err: nil}
}

func (m PluginsModel) update() tea.Msg {
if m.pluginsTable.SelectedRow() == nil {
return types.PluginUpdateMsg{Err: errors.New("No plugin selected")}
}
pluginName := m.pluginsTable.SelectedRow()[0]
cmd := exec.Command("helm", "plugin", "update", pluginName)
err := cmd.Run()

if err != nil {
return types.PluginUpdateMsg{Err: errors.New("Cannot update plugin")}
}
return types.PluginUpdateMsg{Err: nil}
}

func (m PluginsModel) uninstall() tea.Msg {
if m.pluginsTable.SelectedRow() == nil {
return types.PluginUninstallMsg{Err: errors.New("No plugin selected")}
}
pluginName := m.pluginsTable.SelectedRow()[0]
cmd := exec.Command("helm", "plugin", "uninstall", strings.TrimSpace(pluginName))
err := cmd.Run()
if err != nil {
return types.PluginUninstallMsg{Err: errors.New("Cannot update plugin")}
}
return types.PluginUninstallMsg{Err: nil}
}
29 changes: 29 additions & 0 deletions plugins/overview_keymap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package plugins

import "github.com/charmbracelet/bubbles/key"

type keyMap struct {
Install key.Binding
Update key.Binding
Uninstall key.Binding
Cancel key.Binding
Refresh key.Binding
}

var overviewKeys = keyMap{
Uninstall: key.NewBinding(key.WithKeys("U"), key.WithHelp("U", "Uninstall")),
Install: key.NewBinding(key.WithKeys("i"), key.WithHelp("i", "Install")),
Update: key.NewBinding(key.WithKeys("u"), key.WithHelp("u", "Update")),
Cancel: key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "Cancel")),
Refresh: key.NewBinding(key.WithKeys("r"), key.WithHelp("r", "Refresh")),
}

func (k keyMap) ShortHelp() []key.Binding {
return []key.Binding{k.Update, k.Install, k.Uninstall, k.Refresh, k.Cancel}
}

// FullHelp returns keybindings for the expanded help view. It's part of the
// key.Map interface.
func (k keyMap) FullHelp() [][]key.Binding {
return [][]key.Binding{}
}
1 change: 1 addition & 0 deletions plugins/overview_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package plugins
24 changes: 24 additions & 0 deletions plugins/overview_view.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package plugins

import (
"github.com/charmbracelet/lipgloss"
"github.com/pidanou/helmtui/components"
"github.com/pidanou/helmtui/helpers"
"github.com/pidanou/helmtui/styles"
)

func (m PluginsModel) View() string {
var remainingHeight = m.height
if m.installPluginInput.Focused() {
remainingHeight -= 3
}
helperStyle := m.help.Styles.ShortSeparator
helpView := m.help.View(m.keys) + helperStyle.Render(" • ") + m.help.View(helpers.CommonKeys)
view := components.RenderTable(m.pluginsTable, remainingHeight-3, m.width-2)
m.installPluginInput.Width = m.width - 5
if m.installPluginInput.Focused() {
view += "\n" + styles.ActiveStyle.Border(styles.Border).Render(m.installPluginInput.View())
}
view = lipgloss.JoinVertical(lipgloss.Left, view, helpView)
return view
}
Loading

0 comments on commit 5886d87

Please sign in to comment.