Skip to content

Commit

Permalink
Switch out huh for survey with a custom renderer template (#794)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaWilkes authored Nov 8, 2024
1 parent cebcab0 commit fd86607
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 106 deletions.
18 changes: 9 additions & 9 deletions pkg/assume/assume.go
Original file line number Diff line number Diff line change
Expand Up @@ -777,21 +777,21 @@ func QueryProfiles(profiles *cfaws.Profiles) (string, error) {
originalSelectTemplate := survey.SelectQuestionTemplate
survey.SelectQuestionTemplate = fmt.Sprintf(`
{{- define "option"}}
{{- if eq .SelectedIndex .CurrentIndex }}{{color .Config.Icons.SelectFocus.Format }}{{ .Config.Icons.SelectFocus.Text }} {{else}}{{color "default"}} {{end}}
{{- .CurrentOpt.Value}}
{{- color "reset"}}
{{- if eq .SelectedIndex .CurrentIndex }}{{color .Config.Icons.SelectFocus.Format }}{{ .Config.Icons.SelectFocus.Text }} {{else}}{{color "default"}} {{end}}
{{- .CurrentOpt.Value}}{{ if ne ($.GetDescription .CurrentOpt) "" }} - {{color "cyan"}}{{ $.GetDescription .CurrentOpt }}{{end}}
{{- color "reset"}}
{{end}}
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
{{- color "default+hb"}}{{ .Message }}{{ .FilterMessage }}{{color "reset"}}
{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}}
{{- else}}
{{- " "}}{{- color "cyan"}}[Use arrows to move, type to filter{{- if and .Help (not .ShowHelp)}}, {{ .Config.HelpInput }} for more help{{end}}]{{color "reset"}}
{{- "\n"}}
%s
{{- range $ix, $option := .PageEntries}}
{{- template "option" $.IterateOption $ix $option}}
{{- end}}
{{- " "}}{{- color "cyan"}}[Use arrows to move, type to filter{{- if and .Help (not .ShowHelp)}}, {{ .Config.HelpInput }} for more help{{end}}]{{color "reset"}}
{{- "\n"}}
%s{{- "\n"}}
{{- range $ix, $option := .PageEntries}}
{{- template "option" $.IterateOption $ix $option}}
{{- end}}
{{- end}}`, promptHeader)

clio.NewLine()
Expand Down
50 changes: 1 addition & 49 deletions pkg/granted/eks/eks.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@ import (

"connectrpc.com/connect"

"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
"github.com/common-fate/clio"
"github.com/common-fate/grab"
"github.com/common-fate/granted/pkg/cfcfg"
"github.com/common-fate/granted/pkg/granted/proxy"
"github.com/common-fate/sdk/config"
accessv1alpha1 "github.com/common-fate/sdk/gen/commonfate/access/v1alpha1"
"github.com/common-fate/sdk/service/access"
"github.com/mattn/go-runewidth"

"github.com/urfave/cli/v2"
)
Expand Down Expand Up @@ -166,50 +163,5 @@ func promptForClusterAndRole(ctx context.Context, cfg *config.Context) (*accessv
return nil, errors.New("you don't have access to any EKS Clusters")
}

type Column struct {
Title string
Width int
}
cols := []Column{{Title: "Cluster", Width: 40}, {Title: "Role", Width: 40}}
var s = make([]string, 0, len(cols))
for _, col := range cols {
style := lipgloss.NewStyle().Width(col.Width).MaxWidth(col.Width).Inline(true)
renderedCell := style.Render(runewidth.Truncate(col.Title, col.Width, "…"))
s = append(s, lipgloss.NewStyle().Bold(true).Padding(0).Render(renderedCell))
}
header := lipgloss.NewStyle().PaddingLeft(2).Render(lipgloss.JoinHorizontal(lipgloss.Left, s...))
var options []huh.Option[*accessv1alpha1.Entitlement]

for _, entitlement := range entitlements {
style := lipgloss.NewStyle().Width(cols[0].Width).MaxWidth(cols[0].Width).Inline(true)
target := lipgloss.NewStyle().Bold(true).Padding(0).Render(style.Render(runewidth.Truncate(entitlement.Target.Display(), cols[0].Width, "…")))

style = lipgloss.NewStyle().Width(cols[1].Width).MaxWidth(cols[1].Width).Inline(true)
role := lipgloss.NewStyle().Bold(true).Padding(0).Render(style.Render(runewidth.Truncate(entitlement.Role.Display(), cols[1].Width, "…")))

options = append(options, huh.Option[*accessv1alpha1.Entitlement]{
Key: lipgloss.JoinHorizontal(lipgloss.Left, target, role),
Value: entitlement,
})
}

selector := huh.NewSelect[*accessv1alpha1.Entitlement]().
// show the filter dialog when there are 2 or more options
Filtering(len(options) > 1).
Options(options...).
Title("Select a cluster to connect to").
Description(header).WithTheme(huh.ThemeBase())

err = selector.Run()
if err != nil {
return nil, err
}

selectorVal := selector.GetValue()

if selectorVal == nil {
return nil, errors.New("no cluster selected")
}

return selectorVal.(*accessv1alpha1.Entitlement), nil
return proxy.PromptEntitlements(entitlements, "Cluster", "Service Account", "Select a cluster to connect to: ")
}
83 changes: 83 additions & 0 deletions pkg/granted/proxy/prompt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package proxy

import (
"fmt"
"strings"

"github.com/AlecAivazis/survey/v2"
"github.com/charmbracelet/lipgloss"
accessv1alpha1 "github.com/common-fate/sdk/gen/commonfate/access/v1alpha1"
"github.com/mattn/go-runewidth"
)

func filterMultiToken(filterValue string, optValue string, optIndex int) bool {
optValue = strings.ToLower(optValue)
filters := strings.Split(strings.ToLower(filterValue), " ")
for _, filter := range filters {
if !strings.Contains(optValue, filter) {
return false
}
}
return true
}
func PromptEntitlements(entitlements []*accessv1alpha1.Entitlement, targetHeader string, roleHeader string, promptMessage string) (*accessv1alpha1.Entitlement, error) {
type Column struct {
Title string
Width int
}
cols := []Column{{Title: targetHeader, Width: 40}, {Title: roleHeader, Width: 40}}
var s = make([]string, 0, len(cols))
for _, col := range cols {
style := lipgloss.NewStyle().Width(col.Width).MaxWidth(col.Width).Inline(true)
renderedCell := style.Render(runewidth.Truncate(col.Title, col.Width, "…"))
s = append(s, lipgloss.NewStyle().Bold(true).Padding(0).Render(renderedCell))
}
header := lipgloss.NewStyle().PaddingLeft(2).Render(lipgloss.JoinHorizontal(lipgloss.Left, s...))
var options []string
optionsMap := make(map[string]*accessv1alpha1.Entitlement)
for i, entitlement := range entitlements {
style := lipgloss.NewStyle().Width(cols[0].Width).MaxWidth(cols[0].Width).Inline(true)
target := lipgloss.NewStyle().Bold(true).Padding(0).Render(style.Render(runewidth.Truncate(entitlement.Target.Display(), cols[0].Width, "…")))

style = lipgloss.NewStyle().Width(cols[1].Width).MaxWidth(cols[1].Width).Inline(true)
role := lipgloss.NewStyle().Bold(true).Padding(0).Render(style.Render(runewidth.Truncate(entitlement.Role.Display(), cols[1].Width, "…")))

option := lipgloss.JoinHorizontal(lipgloss.Left, target, role)
options = append(options, option)
optionsMap[option] = entitlements[i]
}

originalSelectTemplate := survey.SelectQuestionTemplate
survey.SelectQuestionTemplate = fmt.Sprintf(`
{{- define "option"}}
{{- if eq .SelectedIndex .CurrentIndex }}{{color .Config.Icons.SelectFocus.Format }}{{ .Config.Icons.SelectFocus.Text }} {{else}}{{color "default"}} {{end}}
{{- .CurrentOpt.Value}}{{ if ne ($.GetDescription .CurrentOpt) "" }} - {{color "cyan"}}{{ $.GetDescription .CurrentOpt }}{{end}}
{{- color "reset"}}
{{end}}
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
{{- color "default+hb"}}{{ .Message }}{{ .FilterMessage }}{{color "reset"}}
{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}}
{{- else}}
{{- " "}}{{- color "cyan"}}[Use arrows to move, type to filter{{- if and .Help (not .ShowHelp)}}, {{ .Config.HelpInput }} for more help{{end}}]{{color "reset"}}
{{- "\n"}}
%s{{- "\n"}}
{{- range $ix, $option := .PageEntries}}
{{- template "option" $.IterateOption $ix $option}}
{{- end}}
{{- end}}`, header)

var out string
err := survey.AskOne(&survey.Select{
Message: promptMessage,
Options: options,
Filter: filterMultiToken,
}, &out)
if err != nil {
return nil, err
}

survey.SelectQuestionTemplate = originalSelectTemplate

return optionsMap[out], nil
}
49 changes: 1 addition & 48 deletions pkg/granted/rds/rds.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (

"connectrpc.com/connect"

"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
"github.com/common-fate/clio"
"github.com/common-fate/grab"
"github.com/common-fate/granted/pkg/cfcfg"
Expand All @@ -17,7 +15,6 @@ import (
accessv1alpha1 "github.com/common-fate/sdk/gen/commonfate/access/v1alpha1"
"github.com/common-fate/sdk/service/access"
"github.com/fatih/color"
"github.com/mattn/go-runewidth"

"github.com/urfave/cli/v2"
)
Expand Down Expand Up @@ -162,52 +159,8 @@ func promptForDatabaseAndUser(ctx context.Context, cfg *config.Context) (*access
return nil, errors.New("you don't have access to any RDS databases")
}

type Column struct {
Title string
Width int
}
cols := []Column{{Title: "Database", Width: 40}, {Title: "Role", Width: 40}}
var s = make([]string, 0, len(cols))
for _, col := range cols {
style := lipgloss.NewStyle().Width(col.Width).MaxWidth(col.Width).Inline(true)
renderedCell := style.Render(runewidth.Truncate(col.Title, col.Width, "…"))
s = append(s, lipgloss.NewStyle().Bold(true).Padding(0).Render(renderedCell))
}
header := lipgloss.NewStyle().PaddingLeft(2).Render(lipgloss.JoinHorizontal(lipgloss.Left, s...))
var options []huh.Option[*accessv1alpha1.Entitlement]

for _, entitlement := range entitlements {
style := lipgloss.NewStyle().Width(cols[0].Width).MaxWidth(cols[0].Width).Inline(true)
target := lipgloss.NewStyle().Bold(true).Padding(0).Render(style.Render(runewidth.Truncate(entitlement.Target.Display(), cols[0].Width, "…")))

style = lipgloss.NewStyle().Width(cols[1].Width).MaxWidth(cols[1].Width).Inline(true)
role := lipgloss.NewStyle().Bold(true).Padding(0).Render(style.Render(runewidth.Truncate(entitlement.Role.Display(), cols[1].Width, "…")))

options = append(options, huh.Option[*accessv1alpha1.Entitlement]{
Key: lipgloss.JoinHorizontal(lipgloss.Left, target, role),
Value: entitlement,
})
}

selector := huh.NewSelect[*accessv1alpha1.Entitlement]().
// show the filter dialog when there are 2 or more options
Filtering(len(options) > 1).
Options(options...).
Title("Select a database to connect to").
Description(header).WithTheme(huh.ThemeBase())

err = selector.Run()
if err != nil {
return nil, err
}

selectorVal := selector.GetValue()

if selectorVal == nil {
return nil, errors.New("no database selected")
}
return proxy.PromptEntitlements(entitlements, "Database", "Role", "Select a database to connect to: ")

return selectorVal.(*accessv1alpha1.Entitlement), nil
}

func clientConnectionParameters(c *cli.Context, ensuredAccess *proxy.EnsureAccessOutput[*accessv1alpha1.AWSRDSOutput]) (connectionString, cliString string, port int, err error) {
Expand Down

0 comments on commit fd86607

Please sign in to comment.