Files
Honandmcastorina 47c2b6bed9
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
Add terminal UI (#1593)
* 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]>
2023-08-09 13:13:55 -07:00

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}
}