Lint / lint (push) Waiting to run
Release / Release (push) Waiting to run
Scan for secrets / test (push) Waiting to run
Snifftest / Run Snifftest (push) Waiting to run
Test / test (push) Waiting to run
Test / test-detectors (push) Waiting to run
Test / test-community (push) Waiting to run
* Init attempt at tui with bubble tea. Co-authored-by: mcastorina <[email protected]> * Add starting and source selection options Co-authored-by: mcastorina <[email protected]> * Rewrite models into a state machine * Update source descriptions * Make subpages implement tea.Model * Rename page0 and page1 to be more descriptive * Adjust styling and adding color consts Co-authored-by: mcastorina <[email protected]> * Add helper generic function to call Update and type cast * Setup plumbing for source configuration page * Use CLI introspection for source configuration (WIP) * Experiment with table view * Replace table with form fields Co-authored-by: mcastorina <[email protected]> * Change 🔒 to 💸 * Copy components from soft-serve Co-authored-by: hxnyk <[email protected]> * Copy styles from soft-serve Co-authored-by: hxnyk <[email protected]> * Copy common from soft-serve Co-authored-by: hxnyk <[email protected]> * Refactor into pages This is still a WIP, but the main structure is there. Co-authored-by: hxnyk <[email protected]> * Trying out selector for wizard intro Co-authored-by: mcastorina <[email protected]> * Use selector with custom View Co-authored-by: hxnyk <[email protected]> * Change Item to be an enum Co-authored-by: hxnyk <[email protected]> * Add link pages Co-authored-by: mcastorina <[email protected]> * Update source select to use selector Co-authored-by: mcastorina <[email protected]> * Delete source configure page and add blank tabs Co-authored-by: hxnyk <[email protected]> * Add tab placeholder pages for configurationi Co-authored-by: mcastorina <[email protected]> * Added headers and style to each tab Co-authored-by: hxnyk <[email protected]> * Update with new sources * Remove kingpin attribute from SourceItem * Add basic form field and source structuring * Hookup git form fields with an underlying textinput component Co-authored-by: hxnyk <[email protected]> * Update forms for git and github Co-authored-by: mcastorina <[email protected]> * Add labels per text input * Add sources and adjust styling * add basic trufflehog configuration page * Add skip button to textinputs component * Emit and handle textinputs skip/submit button commands * Don't quit when q is pressed on the sourceConfigurePage * Build trufflehog command based on source config vals Co-authored-by: mcastorina <[email protected]> * Build flags based on truffle config inputs * Update summary section * Add generated truffle fields Co-authored-by: mcastorina <[email protected]> * update summary to correctly print info * Go back a page when escape key is pressed * WIP run page list Co-authored-by: hxnyk <[email protected]> * Allow running trufflehog from the run page Co-authored-by: hxnyk <[email protected]> * Add option to view help docs Co-authored-by: mcastorina <[email protected]> * comment out unused styles and remove unused types * Capitalize H in TruffleHog * remove unneeded fmt.Sprintf --------- Co-authored-by: mcastorina <[email protected]>
238 lines
5.7 KiB
Go
238 lines
5.7 KiB
Go
package selector
|
|
|
|
import (
|
|
"github.com/charmbracelet/bubbles/key"
|
|
"github.com/charmbracelet/bubbles/list"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
|
)
|
|
|
|
// Selector is a list of items that can be selected.
|
|
type Selector struct {
|
|
list.Model
|
|
common common.Common
|
|
active int
|
|
filterState list.FilterState
|
|
}
|
|
|
|
// IdentifiableItem is an item that can be identified by a string. Implements
|
|
// list.DefaultItem.
|
|
type IdentifiableItem interface {
|
|
list.DefaultItem
|
|
ID() string
|
|
}
|
|
|
|
// ItemDelegate is a wrapper around list.ItemDelegate.
|
|
type ItemDelegate interface {
|
|
list.ItemDelegate
|
|
}
|
|
|
|
// SelectMsg is a message that is sent when an item is selected.
|
|
type SelectMsg struct{ IdentifiableItem }
|
|
|
|
// ActiveMsg is a message that is sent when an item is active but not selected.
|
|
type ActiveMsg struct{ IdentifiableItem }
|
|
|
|
// New creates a new selector.
|
|
func New(common common.Common, items []IdentifiableItem, delegate ItemDelegate) *Selector {
|
|
itms := make([]list.Item, len(items))
|
|
for i, item := range items {
|
|
itms[i] = item
|
|
}
|
|
l := list.New(itms, delegate, common.Width, common.Height)
|
|
s := &Selector{
|
|
Model: l,
|
|
common: common,
|
|
}
|
|
s.SetSize(common.Width, common.Height)
|
|
return s
|
|
}
|
|
|
|
// PerPage returns the number of items per page.
|
|
func (s *Selector) PerPage() int {
|
|
return s.Model.Paginator.PerPage
|
|
}
|
|
|
|
// SetPage sets the current page.
|
|
func (s *Selector) SetPage(page int) {
|
|
s.Model.Paginator.Page = page
|
|
}
|
|
|
|
// Page returns the current page.
|
|
func (s *Selector) Page() int {
|
|
return s.Model.Paginator.Page
|
|
}
|
|
|
|
// TotalPages returns the total number of pages.
|
|
func (s *Selector) TotalPages() int {
|
|
return s.Model.Paginator.TotalPages
|
|
}
|
|
|
|
// Select selects the item at the given index.
|
|
func (s *Selector) Select(index int) {
|
|
s.Model.Select(index)
|
|
}
|
|
|
|
// SetShowTitle sets the show title flag.
|
|
func (s *Selector) SetShowTitle(show bool) {
|
|
s.Model.SetShowTitle(show)
|
|
}
|
|
|
|
// SetShowHelp sets the show help flag.
|
|
func (s *Selector) SetShowHelp(show bool) {
|
|
s.Model.SetShowHelp(show)
|
|
}
|
|
|
|
// SetShowStatusBar sets the show status bar flag.
|
|
func (s *Selector) SetShowStatusBar(show bool) {
|
|
s.Model.SetShowStatusBar(show)
|
|
}
|
|
|
|
// DisableQuitKeybindings disables the quit keybindings.
|
|
func (s *Selector) DisableQuitKeybindings() {
|
|
s.Model.DisableQuitKeybindings()
|
|
}
|
|
|
|
// SetShowFilter sets the show filter flag.
|
|
func (s *Selector) SetShowFilter(show bool) {
|
|
s.Model.SetShowFilter(show)
|
|
}
|
|
|
|
// SetShowPagination sets the show pagination flag.
|
|
func (s *Selector) SetShowPagination(show bool) {
|
|
s.Model.SetShowPagination(show)
|
|
}
|
|
|
|
// SetFilteringEnabled sets the filtering enabled flag.
|
|
func (s *Selector) SetFilteringEnabled(enabled bool) {
|
|
s.Model.SetFilteringEnabled(enabled)
|
|
}
|
|
|
|
// SetSize implements common.Component.
|
|
func (s *Selector) SetSize(width, height int) {
|
|
s.common.SetSize(width, height)
|
|
s.Model.SetSize(width, height)
|
|
}
|
|
|
|
// SetItems sets the items in the selector.
|
|
func (s *Selector) SetItems(items []IdentifiableItem) tea.Cmd {
|
|
its := make([]list.Item, len(items))
|
|
for i, item := range items {
|
|
its[i] = item
|
|
}
|
|
return s.Model.SetItems(its)
|
|
}
|
|
|
|
// Index returns the index of the selected item.
|
|
func (s *Selector) Index() int {
|
|
return s.Model.Index()
|
|
}
|
|
|
|
// Init implements tea.Model.
|
|
func (s *Selector) Init() tea.Cmd {
|
|
return s.activeCmd
|
|
}
|
|
|
|
// Update implements tea.Model.
|
|
func (s *Selector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
cmds := make([]tea.Cmd, 0)
|
|
switch msg := msg.(type) {
|
|
case tea.MouseMsg:
|
|
switch msg.Type {
|
|
case tea.MouseWheelUp:
|
|
s.Model.CursorUp()
|
|
case tea.MouseWheelDown:
|
|
s.Model.CursorDown()
|
|
case tea.MouseLeft:
|
|
curIdx := s.Model.Index()
|
|
for i, item := range s.Model.Items() {
|
|
item, _ := item.(IdentifiableItem)
|
|
// Check each item to see if it's in bounds.
|
|
if item != nil && s.common.Zone.Get(item.ID()).InBounds(msg) {
|
|
if i == curIdx {
|
|
cmds = append(cmds, s.selectCmd)
|
|
} else {
|
|
s.Model.Select(i)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
case tea.KeyMsg:
|
|
filterState := s.Model.FilterState()
|
|
switch {
|
|
case key.Matches(msg, s.common.KeyMap.Help):
|
|
if filterState == list.Filtering {
|
|
return s, tea.Batch(cmds...)
|
|
}
|
|
case key.Matches(msg, s.common.KeyMap.Select):
|
|
if filterState != list.Filtering {
|
|
cmds = append(cmds, s.selectCmd)
|
|
}
|
|
}
|
|
case list.FilterMatchesMsg:
|
|
cmds = append(cmds, s.activeFilterCmd)
|
|
}
|
|
m, cmd := s.Model.Update(msg)
|
|
s.Model = m
|
|
if cmd != nil {
|
|
cmds = append(cmds, cmd)
|
|
}
|
|
// Track filter state and update active item when filter state changes.
|
|
filterState := s.Model.FilterState()
|
|
if s.filterState != filterState {
|
|
cmds = append(cmds, s.activeFilterCmd)
|
|
}
|
|
s.filterState = filterState
|
|
// Send ActiveMsg when index change.
|
|
if s.active != s.Model.Index() {
|
|
cmds = append(cmds, s.activeCmd)
|
|
}
|
|
s.active = s.Model.Index()
|
|
return s, tea.Batch(cmds...)
|
|
}
|
|
|
|
// View implements tea.Model.
|
|
func (s *Selector) View() string {
|
|
return s.Model.View()
|
|
}
|
|
|
|
// SelectItem is a command that selects the currently active item.
|
|
func (s *Selector) SelectItem() tea.Msg {
|
|
return s.selectCmd()
|
|
}
|
|
|
|
func (s *Selector) selectCmd() tea.Msg {
|
|
item := s.Model.SelectedItem()
|
|
i, ok := item.(IdentifiableItem)
|
|
if !ok {
|
|
return SelectMsg{}
|
|
}
|
|
return SelectMsg{i}
|
|
}
|
|
|
|
func (s *Selector) activeCmd() tea.Msg {
|
|
item := s.Model.SelectedItem()
|
|
i, ok := item.(IdentifiableItem)
|
|
if !ok {
|
|
return ActiveMsg{}
|
|
}
|
|
return ActiveMsg{i}
|
|
}
|
|
|
|
func (s *Selector) activeFilterCmd() tea.Msg {
|
|
// Here we use VisibleItems because when list.FilterMatchesMsg is sent,
|
|
// VisibleItems is the only way to get the list of filtered items. The list
|
|
// bubble should export something like list.FilterMatchesMsg.Items().
|
|
items := s.Model.VisibleItems()
|
|
if len(items) == 0 {
|
|
return nil
|
|
}
|
|
item := items[0]
|
|
i, ok := item.(IdentifiableItem)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return ActiveMsg{i}
|
|
}
|