mirror of
https://github.com/trufflesecurity/trufflehog.git
synced 2024-11-10 07:04:24 +00:00
Add terminal UI (#1593)
* Init attempt at tui with bubble tea. Co-authored-by: mcastorina <m.castorina93@gmail.com> * Add starting and source selection options Co-authored-by: mcastorina <m.castorina93@gmail.com> * 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 <m.castorina93@gmail.com> * 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 <m.castorina93@gmail.com> * Change 🔒 to 💸 * Copy components from soft-serve Co-authored-by: hxnyk <8292703+hxnyk@users.noreply.github.com> * Copy styles from soft-serve Co-authored-by: hxnyk <8292703+hxnyk@users.noreply.github.com> * Copy common from soft-serve Co-authored-by: hxnyk <8292703+hxnyk@users.noreply.github.com> * Refactor into pages This is still a WIP, but the main structure is there. Co-authored-by: hxnyk <8292703+hxnyk@users.noreply.github.com> * Trying out selector for wizard intro Co-authored-by: mcastorina <m.castorina93@gmail.com> * Use selector with custom View Co-authored-by: hxnyk <8292703+hxnyk@users.noreply.github.com> * Change Item to be an enum Co-authored-by: hxnyk <8292703+hxnyk@users.noreply.github.com> * Add link pages Co-authored-by: mcastorina <m.castorina93@gmail.com> * Update source select to use selector Co-authored-by: mcastorina <m.castorina93@gmail.com> * Delete source configure page and add blank tabs Co-authored-by: hxnyk <8292703+hxnyk@users.noreply.github.com> * Add tab placeholder pages for configurationi Co-authored-by: mcastorina <m.castorina93@gmail.com> * Added headers and style to each tab Co-authored-by: hxnyk <8292703+hxnyk@users.noreply.github.com> * 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 <8292703+hxnyk@users.noreply.github.com> * Update forms for git and github Co-authored-by: mcastorina <m.castorina93@gmail.com> * 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 <m.castorina93@gmail.com> * Build flags based on truffle config inputs * Update summary section * Add generated truffle fields Co-authored-by: mcastorina <m.castorina93@gmail.com> * update summary to correctly print info * Go back a page when escape key is pressed * WIP run page list Co-authored-by: hxnyk <8292703+hxnyk@users.noreply.github.com> * Allow running trufflehog from the run page Co-authored-by: hxnyk <8292703+hxnyk@users.noreply.github.com> * Add option to view help docs Co-authored-by: mcastorina <m.castorina93@gmail.com> * comment out unused styles and remove unused types * Capitalize H in TruffleHog * remove unneeded fmt.Sprintf --------- Co-authored-by: mcastorina <m.castorina93@gmail.com>
This commit is contained in:
parent
e5aeb219de
commit
47c2b6bed9
42 changed files with 3792 additions and 3 deletions
29
go.mod
29
go.mod
|
@ -14,10 +14,15 @@ require (
|
|||
github.com/BobuSumisu/aho-corasick v1.0.3
|
||||
github.com/TheZeroSlave/zapsentry v1.17.0
|
||||
github.com/aws/aws-sdk-go v1.44.83
|
||||
github.com/aymanbagabas/go-osc52 v1.2.1
|
||||
github.com/bill-rich/disk-buffer-reader v0.1.7
|
||||
github.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c
|
||||
github.com/bitfinexcom/bitfinex-api-go v0.0.0-20210608095005-9e0b26f200fb
|
||||
github.com/bradleyfalzon/ghinstallation/v2 v2.6.0
|
||||
github.com/charmbracelet/bubbles v0.16.1
|
||||
github.com/charmbracelet/bubbletea v0.24.1
|
||||
github.com/charmbracelet/glamour v0.6.0
|
||||
github.com/charmbracelet/lipgloss v0.7.1
|
||||
github.com/couchbase/gocb/v2 v2.6.3
|
||||
github.com/crewjam/rfc5424 v0.1.0
|
||||
github.com/denisenkom/go-mssqldb v0.12.3
|
||||
|
@ -45,8 +50,11 @@ require (
|
|||
github.com/jpillora/overseer v1.1.6
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/lrstanley/bubblezone v0.0.0-20221222153816-e95291e2243e
|
||||
github.com/mattn/go-isatty v0.0.18
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/paulbellamy/ratecounter v0.2.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
|
@ -88,9 +96,13 @@ require (
|
|||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/benbjohnson/clock v1.1.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bodgit/plumbing v1.2.0 // indirect
|
||||
|
@ -99,10 +111,12 @@ require (
|
|||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.3 // indirect
|
||||
github.com/connesc/cipherio v0.2.1 // indirect
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/couchbase/gocbcore/v10 v10.2.4 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/docker/cli v23.0.5+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/docker v23.0.5+incompatible // indirect
|
||||
|
@ -125,6 +139,7 @@ require (
|
|||
github.com/google/s2a-go v0.1.4 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
|
@ -135,12 +150,19 @@ require (
|
|||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.16.5 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.23 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.15.1 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/onsi/gomega v1.23.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
|
@ -152,7 +174,9 @@ require (
|
|||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.10.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/skeema/knownhosts v1.2.0 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
|
@ -163,6 +187,8 @@ require (
|
|||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
github.com/yuin/goldmark v1.5.2 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
|
@ -171,6 +197,7 @@ require (
|
|||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.12.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/term v0.10.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
|
|
67
go.sum
67
go.sum
|
@ -68,6 +68,8 @@ github.com/TheZeroSlave/zapsentry v1.17.0 h1:RIQCG89U7vWWZVmmCxeUz/g32WEcAYXUrXH
|
|||
github.com/TheZeroSlave/zapsentry v1.17.0/go.mod h1:D1YMfSuu6xnkhwFXxrronesmsiyDhIqo+86I3Ok+r64=
|
||||
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
|
||||
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
|
||||
|
@ -79,8 +81,17 @@ github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG
|
|||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aws/aws-sdk-go v1.44.83 h1:7+Rtc2Eio6EKUNoZeMV/IVxzVrY5oBQcNPtCcgIHYJA=
|
||||
github.com/aws/aws-sdk-go v1.44.83/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
|
||||
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
|
||||
github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E=
|
||||
github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
|
@ -105,6 +116,14 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
|
|||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
|
||||
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
||||
github.com/charmbracelet/bubbletea v0.24.1 h1:LpdYfnu+Qc6XtvMz6d/6rRY71yttHTP5HtrjMgWvixc=
|
||||
github.com/charmbracelet/bubbletea v0.24.1/go.mod h1:rK3g/2+T8vOSEkNHvtq40umJpeVYDn6bLaqbgzhL/hg=
|
||||
github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc=
|
||||
github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc=
|
||||
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
|
||||
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
|
@ -120,6 +139,8 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
|
|||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
|
||||
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
|
||||
github.com/couchbase/gocb/v2 v2.6.3 h1:5RsMo+RRfK0mVxHLAfpBz3/tHlgXZb1WBNItLk9Ab+c=
|
||||
|
@ -140,6 +161,8 @@ github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+
|
|||
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
|
||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/docker/cli v23.0.5+incompatible h1:ufWmAOuD3Vmr7JP2G5K3cyuNC4YZWiAsuDEvFVVDafE=
|
||||
github.com/docker/cli v23.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
|
@ -288,6 +311,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
|||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
|
@ -343,24 +368,46 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
|||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lrstanley/bubblezone v0.0.0-20221222153816-e95291e2243e h1:XoxHx8K6ZKoMtjzWOMDuM69LCdjDDsTOtTfWGrT/fns=
|
||||
github.com/lrstanley/bubblezone v0.0.0-20221222153816-e95291e2243e/go.mod h1:v5lEwWaguF1o2MW/ucO0ZIA/IZymdBYJJ+2cMRLE7LU=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
|
||||
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
|
||||
github.com/microcosm-cc/bluemonday v1.0.23 h1:SMZe2IGa0NuHvnVNAZ+6B38gsTbi5e4sViiWJyDDqFY=
|
||||
github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA=
|
||||
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
|
||||
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
|
||||
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
|
@ -369,6 +416,8 @@ github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n5
|
|||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
|
@ -410,12 +459,18 @@ github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+Pymzi
|
|||
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||
github.com/rabbitmq/amqp091-go v1.8.1 h1:RejT1SBUim5doqcL6s7iN6SBmsQqyTgXb1xMlH0h1hA=
|
||||
github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
|
@ -471,6 +526,10 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7Jul
|
|||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU=
|
||||
github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
|
||||
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE=
|
||||
|
@ -568,6 +627,7 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
|
@ -628,8 +688,10 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
@ -644,6 +706,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
14
main.go
14
main.go
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/felixge/fgprof"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/jpillora/overseer"
|
||||
"github.com/mattn/go-isatty"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
|
||||
|
@ -28,6 +29,7 @@ import (
|
|||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/sources/git"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/updater"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/version"
|
||||
)
|
||||
|
@ -144,6 +146,18 @@ func init() {
|
|||
}
|
||||
|
||||
cli.Version("trufflehog " + version.BuildVersion)
|
||||
|
||||
if len(os.Args) <= 1 && isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
args := tui.Run()
|
||||
if len(args) == 0 {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Overwrite the Args slice so overseer works properly.
|
||||
os.Args = os.Args[:1]
|
||||
os.Args = append(os.Args, args...)
|
||||
}
|
||||
|
||||
cmd = kingpin.MustParse(cli.Parse(os.Args[1:]))
|
||||
|
||||
switch {
|
||||
|
|
24
pkg/tui/common/common.go
Normal file
24
pkg/tui/common/common.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"github.com/aymanbagabas/go-osc52"
|
||||
zone "github.com/lrstanley/bubblezone"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/keymap"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
|
||||
)
|
||||
|
||||
// Common is a struct all components should embed.
|
||||
type Common struct {
|
||||
Copy *osc52.Output
|
||||
Styles *styles.Styles
|
||||
KeyMap *keymap.KeyMap
|
||||
Width int
|
||||
Height int
|
||||
Zone *zone.Manager
|
||||
}
|
||||
|
||||
// SetSize sets the width and height of the common struct.
|
||||
func (c *Common) SetSize(width, height int) {
|
||||
c.Width = width
|
||||
c.Height = height
|
||||
}
|
13
pkg/tui/common/component.go
Normal file
13
pkg/tui/common/component.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/help"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// Component represents a Bubble Tea model that implements a SetSize function.
|
||||
type Component interface {
|
||||
tea.Model
|
||||
help.KeyMap
|
||||
SetSize(width, height int)
|
||||
}
|
13
pkg/tui/common/error.go
Normal file
13
pkg/tui/common/error.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package common
|
||||
|
||||
import tea "github.com/charmbracelet/bubbletea"
|
||||
|
||||
// ErrorMsg is a Bubble Tea message that represents an error.
|
||||
type ErrorMsg error
|
||||
|
||||
// ErrorCmd returns an ErrorMsg from error.
|
||||
func ErrorCmd(err error) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return ErrorMsg(err)
|
||||
}
|
||||
}
|
27
pkg/tui/common/style.go
Normal file
27
pkg/tui/common/style.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/glamour"
|
||||
gansi "github.com/charmbracelet/glamour/ansi"
|
||||
)
|
||||
|
||||
func strptr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
// StyleConfig returns the default Glamour style configuration.
|
||||
func StyleConfig() gansi.StyleConfig {
|
||||
noColor := strptr("")
|
||||
s := glamour.DarkStyleConfig
|
||||
s.H1.BackgroundColor = noColor
|
||||
s.H1.Prefix = "# "
|
||||
s.H1.Suffix = ""
|
||||
s.H1.Color = strptr("39")
|
||||
s.Document.StylePrimitive.Color = noColor
|
||||
s.CodeBlock.Chroma.Text.Color = noColor
|
||||
s.CodeBlock.Chroma.Name.Color = noColor
|
||||
// This fixes an issue with the default style config. For example
|
||||
// highlighting empty spaces with red in Dockerfile type.
|
||||
s.CodeBlock.Chroma.Error.BackgroundColor = noColor
|
||||
return s
|
||||
}
|
27
pkg/tui/common/utils.go
Normal file
27
pkg/tui/common/utils.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/muesli/reflow/truncate"
|
||||
)
|
||||
|
||||
// TruncateString is a convenient wrapper around truncate.TruncateString.
|
||||
func TruncateString(s string, max int) string {
|
||||
if max < 0 {
|
||||
max = 0
|
||||
}
|
||||
return truncate.StringWithTail(s, uint(max), "…")
|
||||
}
|
||||
|
||||
func SummarizeSource(keys []string, inputs map[string]string, labels map[string]string) string {
|
||||
summary := strings.Builder{}
|
||||
for _, key := range keys {
|
||||
if inputs[key] != "" {
|
||||
summary.WriteString("\t" + labels[key] + ": " + inputs[key] + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
summary.WriteString("\n")
|
||||
return summary.String()
|
||||
}
|
96
pkg/tui/components/footer/footer.go
Normal file
96
pkg/tui/components/footer/footer.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package footer
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/help"
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
)
|
||||
|
||||
// ToggleFooterMsg is a message sent to show/hide the footer.
|
||||
type ToggleFooterMsg struct{}
|
||||
|
||||
// Footer is a Bubble Tea model that displays help and other info.
|
||||
type Footer struct {
|
||||
common common.Common
|
||||
help help.Model
|
||||
keymap help.KeyMap
|
||||
}
|
||||
|
||||
// New creates a new Footer.
|
||||
func New(c common.Common, keymap help.KeyMap) *Footer {
|
||||
h := help.New()
|
||||
h.Styles.ShortKey = c.Styles.HelpKey
|
||||
h.Styles.ShortDesc = c.Styles.HelpValue
|
||||
h.Styles.FullKey = c.Styles.HelpKey
|
||||
h.Styles.FullDesc = c.Styles.HelpValue
|
||||
f := &Footer{
|
||||
common: c,
|
||||
help: h,
|
||||
keymap: keymap,
|
||||
}
|
||||
f.SetSize(c.Width, c.Height)
|
||||
return f
|
||||
}
|
||||
|
||||
// SetSize implements common.Component.
|
||||
func (f *Footer) SetSize(width, height int) {
|
||||
f.common.SetSize(width, height)
|
||||
f.help.Width = width -
|
||||
f.common.Styles.Footer.GetHorizontalFrameSize()
|
||||
}
|
||||
|
||||
// Init implements tea.Model.
|
||||
func (f *Footer) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update implements tea.Model.
|
||||
func (f *Footer) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// View implements tea.Model.
|
||||
func (f *Footer) View() string {
|
||||
if f.keymap == nil {
|
||||
return ""
|
||||
}
|
||||
s := f.common.Styles.Footer.Copy().
|
||||
Width(f.common.Width)
|
||||
helpView := f.help.View(f.keymap)
|
||||
return f.common.Zone.Mark(
|
||||
"footer",
|
||||
s.Render(helpView),
|
||||
)
|
||||
}
|
||||
|
||||
// ShortHelp returns the short help key bindings.
|
||||
func (f *Footer) ShortHelp() []key.Binding {
|
||||
return f.keymap.ShortHelp()
|
||||
}
|
||||
|
||||
// FullHelp returns the full help key bindings.
|
||||
func (f *Footer) FullHelp() [][]key.Binding {
|
||||
return f.keymap.FullHelp()
|
||||
}
|
||||
|
||||
// ShowAll returns whether the full help is shown.
|
||||
func (f *Footer) ShowAll() bool {
|
||||
return f.help.ShowAll
|
||||
}
|
||||
|
||||
// SetShowAll sets whether the full help is shown.
|
||||
func (f *Footer) SetShowAll(show bool) {
|
||||
f.help.ShowAll = show
|
||||
}
|
||||
|
||||
// Height returns the height of the footer.
|
||||
func (f *Footer) Height() int {
|
||||
return lipgloss.Height(f.View())
|
||||
}
|
||||
|
||||
// ToggleFooterCmd sends a ToggleFooterMsg to show/hide the help footer.
|
||||
func ToggleFooterCmd() tea.Msg {
|
||||
return ToggleFooterMsg{}
|
||||
}
|
38
pkg/tui/components/formfield/formfield.go
Normal file
38
pkg/tui/components/formfield/formfield.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package formfield
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
|
||||
)
|
||||
|
||||
type FormField struct {
|
||||
Label string
|
||||
Required bool
|
||||
Help string
|
||||
Component tea.Model
|
||||
}
|
||||
|
||||
func NewFormField(common common.Common) *FormField {
|
||||
return &FormField{}
|
||||
}
|
||||
|
||||
func (field *FormField) ViewLabel() string {
|
||||
var label strings.Builder
|
||||
if field.Required {
|
||||
label.WriteString(styles.BoldTextStyle.Render(field.Label) + "*\n")
|
||||
} else {
|
||||
label.WriteString(styles.BoldTextStyle.Render(field.Label) + "\n")
|
||||
}
|
||||
|
||||
return label.String()
|
||||
}
|
||||
|
||||
func (field *FormField) ViewHelp() string {
|
||||
var help strings.Builder
|
||||
help.WriteString(styles.HintTextStyle.Render(field.Help) + "\n")
|
||||
|
||||
return help.String()
|
||||
}
|
42
pkg/tui/components/header/header.go
Normal file
42
pkg/tui/components/header/header.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package header
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
)
|
||||
|
||||
// Header represents a header component.
|
||||
type Header struct {
|
||||
common common.Common
|
||||
text string
|
||||
}
|
||||
|
||||
// New creates a new header component.
|
||||
func New(c common.Common, text string) *Header {
|
||||
return &Header{
|
||||
common: c,
|
||||
text: text,
|
||||
}
|
||||
}
|
||||
|
||||
// SetSize implements common.Component.
|
||||
func (h *Header) SetSize(width, height int) {
|
||||
h.common.SetSize(width, height)
|
||||
}
|
||||
|
||||
// Init implements tea.Model.
|
||||
func (h *Header) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update implements tea.Model.
|
||||
func (h *Header) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// View implements tea.Model.
|
||||
func (h *Header) View() string {
|
||||
return h.common.Styles.ServerName.Render(strings.TrimSpace(h.text))
|
||||
}
|
237
pkg/tui/components/selector/selector.go
Normal file
237
pkg/tui/components/selector/selector.go
Normal file
|
@ -0,0 +1,237 @@
|
|||
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}
|
||||
}
|
88
pkg/tui/components/statusbar/statusbar.go
Normal file
88
pkg/tui/components/statusbar/statusbar.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
package statusbar
|
||||
|
||||
import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/muesli/reflow/truncate"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
)
|
||||
|
||||
// StatusBarMsg is a message sent to the status bar.
|
||||
type StatusBarMsg struct {
|
||||
Key string
|
||||
Value string
|
||||
Info string
|
||||
Branch string
|
||||
}
|
||||
|
||||
// StatusBar is a status bar model.
|
||||
type StatusBar struct {
|
||||
common common.Common
|
||||
msg StatusBarMsg
|
||||
}
|
||||
|
||||
// Model is an interface that supports setting the status bar information.
|
||||
type Model interface {
|
||||
StatusBarValue() string
|
||||
StatusBarInfo() string
|
||||
}
|
||||
|
||||
// New creates a new status bar component.
|
||||
func New(c common.Common) *StatusBar {
|
||||
s := &StatusBar{
|
||||
common: c,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// SetSize implements common.Component.
|
||||
func (s *StatusBar) SetSize(width, height int) {
|
||||
s.common.Width = width
|
||||
s.common.Height = height
|
||||
}
|
||||
|
||||
// Init implements tea.Model.
|
||||
func (s *StatusBar) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update implements tea.Model.
|
||||
func (s *StatusBar) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case StatusBarMsg:
|
||||
s.msg = msg
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// View implements tea.Model.
|
||||
func (s *StatusBar) View() string {
|
||||
st := s.common.Styles
|
||||
w := lipgloss.Width
|
||||
help := s.common.Zone.Mark(
|
||||
"repo-help",
|
||||
st.StatusBarHelp.Render("? Help"),
|
||||
)
|
||||
key := st.StatusBarKey.Render(s.msg.Key)
|
||||
info := ""
|
||||
if s.msg.Info != "" {
|
||||
info = st.StatusBarInfo.Render(s.msg.Info)
|
||||
}
|
||||
branch := st.StatusBarBranch.Render(s.msg.Branch)
|
||||
maxWidth := s.common.Width - w(key) - w(info) - w(branch) - w(help)
|
||||
v := truncate.StringWithTail(s.msg.Value, uint(maxWidth-st.StatusBarValue.GetHorizontalFrameSize()), "…")
|
||||
value := st.StatusBarValue.
|
||||
Width(maxWidth).
|
||||
Render(v)
|
||||
|
||||
return lipgloss.NewStyle().MaxWidth(s.common.Width).
|
||||
Render(
|
||||
lipgloss.JoinHorizontal(lipgloss.Top,
|
||||
key,
|
||||
value,
|
||||
info,
|
||||
branch,
|
||||
help,
|
||||
),
|
||||
)
|
||||
}
|
122
pkg/tui/components/tabs/tabs.go
Normal file
122
pkg/tui/components/tabs/tabs.go
Normal file
|
@ -0,0 +1,122 @@
|
|||
package tabs
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
)
|
||||
|
||||
// SelectTabMsg is a message that contains the index of the tab to select.
|
||||
type SelectTabMsg int
|
||||
|
||||
// ActiveTabMsg is a message that contains the index of the current active tab.
|
||||
type ActiveTabMsg int
|
||||
|
||||
// Tabs is bubbletea component that displays a list of tabs.
|
||||
type Tabs struct {
|
||||
common common.Common
|
||||
tabs []string
|
||||
activeTab int
|
||||
TabSeparator lipgloss.Style
|
||||
TabInactive lipgloss.Style
|
||||
TabActive lipgloss.Style
|
||||
TabDot lipgloss.Style
|
||||
UseDot bool
|
||||
}
|
||||
|
||||
// New creates a new Tabs component.
|
||||
func New(c common.Common, tabs []string) *Tabs {
|
||||
r := &Tabs{
|
||||
common: c,
|
||||
tabs: tabs,
|
||||
activeTab: 0,
|
||||
TabSeparator: c.Styles.TabSeparator,
|
||||
TabInactive: c.Styles.TabInactive,
|
||||
TabActive: c.Styles.TabActive,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// SetSize implements common.Component.
|
||||
func (t *Tabs) SetSize(width, height int) {
|
||||
t.common.SetSize(width, height)
|
||||
}
|
||||
|
||||
// Init implements tea.Model.
|
||||
func (t *Tabs) Init() tea.Cmd {
|
||||
t.activeTab = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update implements tea.Model.
|
||||
func (t *Tabs) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
cmds := make([]tea.Cmd, 0)
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "tab":
|
||||
t.activeTab = (t.activeTab + 1) % len(t.tabs)
|
||||
cmds = append(cmds, t.activeTabCmd)
|
||||
case "shift+tab":
|
||||
t.activeTab = (t.activeTab - 1 + len(t.tabs)) % len(t.tabs)
|
||||
cmds = append(cmds, t.activeTabCmd)
|
||||
}
|
||||
case tea.MouseMsg:
|
||||
if msg.Type == tea.MouseLeft {
|
||||
for i, tab := range t.tabs {
|
||||
if t.common.Zone.Get(tab).InBounds(msg) {
|
||||
t.activeTab = i
|
||||
cmds = append(cmds, t.activeTabCmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
case SelectTabMsg:
|
||||
tab := int(msg)
|
||||
if tab >= 0 && tab < len(t.tabs) {
|
||||
t.activeTab = int(msg)
|
||||
}
|
||||
}
|
||||
return t, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// View implements tea.Model.
|
||||
func (t *Tabs) View() string {
|
||||
s := strings.Builder{}
|
||||
sep := t.TabSeparator
|
||||
for i, tab := range t.tabs {
|
||||
style := t.TabInactive.Copy()
|
||||
prefix := " "
|
||||
if i == t.activeTab {
|
||||
style = t.TabActive.Copy()
|
||||
prefix = t.TabDot.Render("• ")
|
||||
}
|
||||
if t.UseDot {
|
||||
s.WriteString(prefix)
|
||||
}
|
||||
s.WriteString(
|
||||
t.common.Zone.Mark(
|
||||
tab,
|
||||
style.Render(tab),
|
||||
),
|
||||
)
|
||||
if i != len(t.tabs)-1 {
|
||||
s.WriteString(sep.String())
|
||||
}
|
||||
}
|
||||
return lipgloss.NewStyle().
|
||||
MaxWidth(t.common.Width).
|
||||
Render(s.String())
|
||||
}
|
||||
|
||||
func (t *Tabs) activeTabCmd() tea.Msg {
|
||||
return ActiveTabMsg(t.activeTab)
|
||||
}
|
||||
|
||||
// SelectTabCmd is a bubbletea command that selects the tab at the given index.
|
||||
func SelectTabCmd(tab int) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return SelectTabMsg(tab)
|
||||
}
|
||||
}
|
51
pkg/tui/components/textinput/textinput.go
Normal file
51
pkg/tui/components/textinput/textinput.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package textinput
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
type (
|
||||
errMsg error
|
||||
)
|
||||
|
||||
type TextInput struct {
|
||||
textInput textinput.Model
|
||||
err error
|
||||
}
|
||||
|
||||
func New(placeholder string) TextInput {
|
||||
ti := textinput.New()
|
||||
ti.Placeholder = placeholder
|
||||
ti.Focus()
|
||||
ti.CharLimit = 156
|
||||
ti.Width = 60
|
||||
|
||||
return TextInput{
|
||||
textInput: ti,
|
||||
err: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func (m TextInput) Init() tea.Cmd {
|
||||
return textinput.Blink
|
||||
}
|
||||
|
||||
func (m TextInput) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
|
||||
// We handle errors just like any other message
|
||||
case errMsg:
|
||||
m.err = msg
|
||||
return m, nil
|
||||
}
|
||||
|
||||
m.textInput, cmd = m.textInput.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m TextInput) View() string {
|
||||
return m.textInput.View()
|
||||
}
|
229
pkg/tui/components/textinputs/textinputs.go
Normal file
229
pkg/tui/components/textinputs/textinputs.go
Normal file
|
@ -0,0 +1,229 @@
|
|||
package textinputs
|
||||
|
||||
// from https://github.com/charmbracelet/bubbletea/blob/master/examples/textinputs/main.go
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
var (
|
||||
focusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
|
||||
blurredStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
|
||||
noStyle = lipgloss.NewStyle()
|
||||
helpStyle = blurredStyle.Copy()
|
||||
// cursorStyle = focusedStyle.Copy()
|
||||
// cursorModeHelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("244"))
|
||||
|
||||
focusedButton = focusedStyle.Copy().Render("[ Next ]")
|
||||
blurredButton = fmt.Sprintf("[ %s ]", blurredStyle.Render("Next"))
|
||||
focusedSkipButton = lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Render("[ Run with defaults ]")
|
||||
blurredSkipButton = fmt.Sprintf("[ %s ]", lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render("Run with defaults"))
|
||||
)
|
||||
|
||||
// SelectNextMsg used for emitting events when the 'Next' button is selected.
|
||||
type SelectNextMsg int
|
||||
|
||||
// SelectSkipMsg used for emitting events when the 'Skip' button is selected.
|
||||
type SelectSkipMsg int
|
||||
|
||||
type Model struct {
|
||||
focusIndex int
|
||||
inputs []textinput.Model
|
||||
configs []InputConfig
|
||||
// cursorMode cursor.Mode
|
||||
skipButton bool
|
||||
}
|
||||
|
||||
type InputConfig struct {
|
||||
Label string
|
||||
Key string
|
||||
Help string
|
||||
Required bool
|
||||
Placeholder string
|
||||
}
|
||||
|
||||
func (m Model) GetInputs() map[string]string {
|
||||
inputs := make(map[string]string)
|
||||
|
||||
for i, input := range m.inputs {
|
||||
inputs[m.configs[i].Key] = input.Value()
|
||||
}
|
||||
|
||||
return inputs
|
||||
}
|
||||
|
||||
func (m Model) GetLabels() map[string]string {
|
||||
labels := make(map[string]string)
|
||||
|
||||
for _, config := range m.configs {
|
||||
labels[config.Key] = config.Label
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
|
||||
func New(config []InputConfig) Model {
|
||||
m := Model{
|
||||
inputs: make([]textinput.Model, len(config)),
|
||||
}
|
||||
|
||||
for i, conf := range config {
|
||||
input := textinput.New()
|
||||
input.Placeholder = conf.Placeholder
|
||||
|
||||
if i == 0 {
|
||||
input.Focus()
|
||||
input.TextStyle = focusedStyle
|
||||
input.PromptStyle = focusedStyle
|
||||
}
|
||||
|
||||
m.inputs[i] = input
|
||||
}
|
||||
|
||||
m.configs = config
|
||||
return m
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return textinput.Blink
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
// Set focus to next input
|
||||
case "enter", "up", "down":
|
||||
s := msg.String()
|
||||
|
||||
// Did the user press enter while the submit or skip button was focused?
|
||||
// If so, emit the appropriate command.
|
||||
if s == "enter" && m.focusIndex == len(m.inputs) {
|
||||
return m, func() tea.Msg { return SelectNextMsg(0) }
|
||||
} else if s == "enter" && m.focusIndex == -1 {
|
||||
return m, func() tea.Msg { return SelectSkipMsg(0) }
|
||||
}
|
||||
|
||||
// Cycle indexes
|
||||
if s == "up" {
|
||||
m.focusIndex--
|
||||
} else {
|
||||
m.focusIndex++
|
||||
}
|
||||
|
||||
if m.focusIndex > len(m.inputs) {
|
||||
m.focusIndex = 0
|
||||
} else if !m.skipButton && m.focusIndex < 0 {
|
||||
m.focusIndex = len(m.inputs)
|
||||
} else if m.skipButton && m.focusIndex < -1 {
|
||||
m.focusIndex = len(m.inputs)
|
||||
}
|
||||
|
||||
cmds := make([]tea.Cmd, len(m.inputs))
|
||||
for i := 0; i < len(m.inputs); i++ {
|
||||
if i == m.focusIndex {
|
||||
// Set focused state
|
||||
cmds[i] = m.focusInput(i)
|
||||
continue
|
||||
}
|
||||
// Remove focused state
|
||||
m.unfocusInput(i)
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle character input and blinking
|
||||
cmd := m.updateInputs(msg)
|
||||
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m *Model) updateInputs(msg tea.Msg) tea.Cmd {
|
||||
cmds := make([]tea.Cmd, len(m.inputs))
|
||||
|
||||
// Only text inputs with Focus() set will respond, so it's safe to simply
|
||||
// update all of them here without any further logic.
|
||||
for i := range m.inputs {
|
||||
m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
|
||||
}
|
||||
|
||||
return tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
var b strings.Builder
|
||||
|
||||
if m.skipButton {
|
||||
button := &blurredSkipButton
|
||||
if m.focusIndex == -1 {
|
||||
button = &focusedSkipButton
|
||||
}
|
||||
fmt.Fprintf(&b, "%s\n\n\n", *button)
|
||||
}
|
||||
|
||||
for i := range m.inputs {
|
||||
if m.configs[i].Label != "" {
|
||||
b.WriteString(m.GetLabel(m.configs[i]))
|
||||
}
|
||||
|
||||
b.WriteString(m.inputs[i].View())
|
||||
b.WriteRune('\n')
|
||||
if i < len(m.inputs)-1 {
|
||||
b.WriteRune('\n')
|
||||
}
|
||||
}
|
||||
|
||||
button := &blurredButton
|
||||
if m.focusIndex == len(m.inputs) {
|
||||
button = &focusedButton
|
||||
}
|
||||
fmt.Fprintf(&b, "\n\n%s\n\n", *button)
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m Model) GetLabel(c InputConfig) string {
|
||||
var label strings.Builder
|
||||
|
||||
label.WriteString(c.Label)
|
||||
if c.Required {
|
||||
label.WriteString("*")
|
||||
}
|
||||
|
||||
if len(c.Help) > 0 {
|
||||
label.WriteString("\n" + helpStyle.Render(c.Help))
|
||||
}
|
||||
|
||||
label.WriteString("\n")
|
||||
return label.String()
|
||||
}
|
||||
|
||||
func (m Model) SetSkip(skip bool) Model {
|
||||
m.skipButton = skip
|
||||
if m.skipButton {
|
||||
if len(m.inputs) > 0 {
|
||||
m.unfocusInput(0)
|
||||
}
|
||||
m.focusIndex = -1
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Model) unfocusInput(index int) {
|
||||
m.inputs[index].Blur()
|
||||
m.inputs[index].PromptStyle = noStyle
|
||||
m.inputs[index].TextStyle = noStyle
|
||||
}
|
||||
|
||||
func (m *Model) focusInput(index int) tea.Cmd {
|
||||
m.inputs[index].PromptStyle = focusedStyle
|
||||
m.inputs[index].TextStyle = focusedStyle
|
||||
return m.inputs[index].Focus()
|
||||
}
|
97
pkg/tui/components/viewport/viewport.go
Normal file
97
pkg/tui/components/viewport/viewport.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package viewport
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
)
|
||||
|
||||
// Viewport represents a viewport component.
|
||||
type Viewport struct {
|
||||
common common.Common
|
||||
*viewport.Model
|
||||
}
|
||||
|
||||
// New returns a new Viewport.
|
||||
func New(c common.Common) *Viewport {
|
||||
vp := viewport.New(c.Width, c.Height)
|
||||
vp.MouseWheelEnabled = true
|
||||
return &Viewport{
|
||||
common: c,
|
||||
Model: &vp,
|
||||
}
|
||||
}
|
||||
|
||||
// SetSize implements common.Component.
|
||||
func (v *Viewport) SetSize(width, height int) {
|
||||
v.common.SetSize(width, height)
|
||||
v.Model.Width = width
|
||||
v.Model.Height = height
|
||||
}
|
||||
|
||||
// Init implements tea.Model.
|
||||
func (v *Viewport) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update implements tea.Model.
|
||||
func (v *Viewport) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
vp, cmd := v.Model.Update(msg)
|
||||
v.Model = &vp
|
||||
return v, cmd
|
||||
}
|
||||
|
||||
// View implements tea.Model.
|
||||
func (v *Viewport) View() string {
|
||||
return v.Model.View()
|
||||
}
|
||||
|
||||
// SetContent sets the viewport's content.
|
||||
func (v *Viewport) SetContent(content string) {
|
||||
v.Model.SetContent(content)
|
||||
}
|
||||
|
||||
// GotoTop moves the viewport to the top of the log.
|
||||
func (v *Viewport) GotoTop() {
|
||||
v.Model.GotoTop()
|
||||
}
|
||||
|
||||
// GotoBottom moves the viewport to the bottom of the log.
|
||||
func (v *Viewport) GotoBottom() {
|
||||
v.Model.GotoBottom()
|
||||
}
|
||||
|
||||
// HalfViewDown moves the viewport down by half the viewport height.
|
||||
func (v *Viewport) HalfViewDown() {
|
||||
v.Model.HalfViewDown()
|
||||
}
|
||||
|
||||
// HalfViewUp moves the viewport up by half the viewport height.
|
||||
func (v *Viewport) HalfViewUp() {
|
||||
v.Model.HalfViewUp()
|
||||
}
|
||||
|
||||
// ViewUp moves the viewport up by a page.
|
||||
func (v *Viewport) ViewUp() []string {
|
||||
return v.Model.ViewUp()
|
||||
}
|
||||
|
||||
// ViewDown moves the viewport down by a page.
|
||||
func (v *Viewport) ViewDown() []string {
|
||||
return v.Model.ViewDown()
|
||||
}
|
||||
|
||||
// LineUp moves the viewport up by the given number of lines.
|
||||
func (v *Viewport) LineUp(n int) []string {
|
||||
return v.Model.LineUp(n)
|
||||
}
|
||||
|
||||
// LineDown moves the viewport down by the given number of lines.
|
||||
func (v *Viewport) LineDown(n int) []string {
|
||||
return v.Model.LineDown(n)
|
||||
}
|
||||
|
||||
// ScrollPercent returns the viewport's scroll percentage.
|
||||
func (v *Viewport) ScrollPercent() float64 {
|
||||
return v.Model.ScrollPercent()
|
||||
}
|
217
pkg/tui/keymap/keymap.go
Normal file
217
pkg/tui/keymap/keymap.go
Normal file
|
@ -0,0 +1,217 @@
|
|||
package keymap
|
||||
|
||||
import "github.com/charmbracelet/bubbles/key"
|
||||
|
||||
// KeyMap is a map of key bindings for the UI.
|
||||
type KeyMap struct {
|
||||
Quit key.Binding
|
||||
CmdQuit key.Binding
|
||||
Up key.Binding
|
||||
Down key.Binding
|
||||
UpDown key.Binding
|
||||
LeftRight key.Binding
|
||||
Arrows key.Binding
|
||||
Select key.Binding
|
||||
Section key.Binding
|
||||
Back key.Binding
|
||||
PrevPage key.Binding
|
||||
NextPage key.Binding
|
||||
Help key.Binding
|
||||
|
||||
SelectItem key.Binding
|
||||
BackItem key.Binding
|
||||
|
||||
Copy key.Binding
|
||||
}
|
||||
|
||||
// DefaultKeyMap returns the default key map.
|
||||
func DefaultKeyMap() *KeyMap {
|
||||
km := new(KeyMap)
|
||||
|
||||
km.Quit = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"ctrl+c",
|
||||
),
|
||||
key.WithHelp(
|
||||
"ctrl+c",
|
||||
"quit",
|
||||
),
|
||||
)
|
||||
|
||||
km.CmdQuit = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"q",
|
||||
"ctrl+c",
|
||||
),
|
||||
key.WithHelp(
|
||||
"q",
|
||||
"quit",
|
||||
),
|
||||
)
|
||||
|
||||
km.Up = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"up",
|
||||
"k",
|
||||
),
|
||||
key.WithHelp(
|
||||
"↑/k",
|
||||
"up",
|
||||
),
|
||||
)
|
||||
|
||||
km.Down = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"down",
|
||||
"j",
|
||||
),
|
||||
key.WithHelp(
|
||||
"↓/j",
|
||||
"down",
|
||||
),
|
||||
)
|
||||
|
||||
km.UpDown = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"up",
|
||||
"down",
|
||||
"k",
|
||||
"j",
|
||||
),
|
||||
key.WithHelp(
|
||||
"↑↓",
|
||||
"navigate",
|
||||
),
|
||||
)
|
||||
|
||||
km.LeftRight = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"left",
|
||||
"h",
|
||||
"right",
|
||||
"l",
|
||||
),
|
||||
key.WithHelp(
|
||||
"←→",
|
||||
"navigate",
|
||||
),
|
||||
)
|
||||
|
||||
km.Arrows = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"up",
|
||||
"right",
|
||||
"down",
|
||||
"left",
|
||||
"k",
|
||||
"j",
|
||||
"h",
|
||||
"l",
|
||||
),
|
||||
key.WithHelp(
|
||||
"↑←↓→",
|
||||
"navigate",
|
||||
),
|
||||
)
|
||||
|
||||
km.Select = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"enter",
|
||||
),
|
||||
key.WithHelp(
|
||||
"enter",
|
||||
"select",
|
||||
),
|
||||
)
|
||||
|
||||
km.Section = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"tab",
|
||||
"shift+tab",
|
||||
),
|
||||
key.WithHelp(
|
||||
"tab",
|
||||
"section",
|
||||
),
|
||||
)
|
||||
|
||||
km.Back = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"esc",
|
||||
),
|
||||
key.WithHelp(
|
||||
"esc",
|
||||
"back",
|
||||
),
|
||||
)
|
||||
|
||||
km.PrevPage = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"pgup",
|
||||
"b",
|
||||
"u",
|
||||
),
|
||||
key.WithHelp(
|
||||
"pgup",
|
||||
"prev page",
|
||||
),
|
||||
)
|
||||
|
||||
km.NextPage = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"pgdown",
|
||||
"f",
|
||||
"d",
|
||||
),
|
||||
key.WithHelp(
|
||||
"pgdn",
|
||||
"next page",
|
||||
),
|
||||
)
|
||||
|
||||
km.Help = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"?",
|
||||
),
|
||||
key.WithHelp(
|
||||
"?",
|
||||
"toggle help",
|
||||
),
|
||||
)
|
||||
|
||||
km.SelectItem = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"l",
|
||||
"right",
|
||||
),
|
||||
key.WithHelp(
|
||||
"→",
|
||||
"select",
|
||||
),
|
||||
)
|
||||
|
||||
km.BackItem = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"h",
|
||||
"left",
|
||||
"backspace",
|
||||
),
|
||||
key.WithHelp(
|
||||
"←",
|
||||
"back",
|
||||
),
|
||||
)
|
||||
|
||||
km.Copy = key.NewBinding(
|
||||
key.WithKeys(
|
||||
"c",
|
||||
"ctrl+c",
|
||||
),
|
||||
key.WithHelp(
|
||||
"c",
|
||||
"copy text",
|
||||
),
|
||||
)
|
||||
|
||||
return km
|
||||
}
|
60
pkg/tui/pages/contact_enterprise/contact_enterprise.go
Normal file
60
pkg/tui/pages/contact_enterprise/contact_enterprise.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package contact_enterprise
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
|
||||
)
|
||||
|
||||
type ContactEnterprise struct {
|
||||
common.Common
|
||||
viewed bool
|
||||
}
|
||||
|
||||
var (
|
||||
linkStyle = lipgloss.NewStyle().Foreground(
|
||||
lipgloss.Color("28")) // green
|
||||
)
|
||||
|
||||
func New(c common.Common) *ContactEnterprise {
|
||||
return &ContactEnterprise{
|
||||
Common: c,
|
||||
viewed: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ContactEnterprise) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ContactEnterprise) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.viewed {
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
return m, func() tea.Msg { return nil }
|
||||
}
|
||||
|
||||
func (m *ContactEnterprise) View() string {
|
||||
|
||||
s := strings.Builder{}
|
||||
s.WriteString("Interested in Trufflehog enterprise?\n")
|
||||
s.WriteString(linkStyle.Render("🔗 https://trufflesecurity.com/contact"))
|
||||
|
||||
m.viewed = true
|
||||
return styles.AppStyle.Render(s.String())
|
||||
}
|
||||
|
||||
func (m *ContactEnterprise) ShortHelp() []key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ContactEnterprise) FullHelp() [][]key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
23
pkg/tui/pages/source_configure/item.go
Normal file
23
pkg/tui/pages/source_configure/item.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package source_configure
|
||||
|
||||
type Item struct {
|
||||
title string
|
||||
description string
|
||||
}
|
||||
|
||||
func (i Item) ID() string { return i.title }
|
||||
|
||||
func (i Item) Title() string {
|
||||
return i.title
|
||||
}
|
||||
func (i Item) Description() string {
|
||||
return i.description
|
||||
}
|
||||
|
||||
func (i Item) SetDescription(d string) Item {
|
||||
i.description = d
|
||||
return i
|
||||
}
|
||||
|
||||
// We shouldn't be filtering for these list items.
|
||||
func (i Item) FilterValue() string { return "" }
|
118
pkg/tui/pages/source_configure/run_component.go
Normal file
118
pkg/tui/pages/source_configure/run_component.go
Normal file
|
@ -0,0 +1,118 @@
|
|||
package source_configure
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
|
||||
)
|
||||
|
||||
type SetArgsMsg string
|
||||
|
||||
type RunComponent struct {
|
||||
common.Common
|
||||
parent *SourceConfigure
|
||||
reviewList list.Model
|
||||
reviewListItems []list.Item
|
||||
}
|
||||
|
||||
func NewRunComponent(common common.Common, parent *SourceConfigure) *RunComponent {
|
||||
// Make list of SourceItems.
|
||||
listItems := []list.Item{
|
||||
Item{title: "🔎 Source configuration"},
|
||||
Item{title: "🐽 TruffleHog configuration"},
|
||||
Item{title: "💸 Sales pitch", description: "\tContinuous monitoring, state tracking, remediations, and more\n\t🔗 https://trufflesecurity.com/trufflehog"},
|
||||
}
|
||||
|
||||
// Setup list
|
||||
delegate := list.NewDefaultDelegate()
|
||||
delegate.Styles.SelectedTitle.Foreground(lipgloss.Color("white"))
|
||||
delegate.Styles.SelectedDesc.Foreground(lipgloss.Color("white"))
|
||||
delegate.SetHeight(3)
|
||||
|
||||
reviewList := list.New(listItems, delegate, common.Width, common.Height)
|
||||
|
||||
reviewList.SetShowTitle(false)
|
||||
reviewList.SetShowStatusBar(false)
|
||||
reviewList.SetFilteringEnabled(false)
|
||||
|
||||
return &RunComponent{
|
||||
Common: common,
|
||||
parent: parent,
|
||||
reviewList: reviewList,
|
||||
reviewListItems: listItems,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *RunComponent) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RunComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
h, v := styles.AppStyle.GetFrameSize()
|
||||
m.reviewList.SetSize(msg.Width-h, msg.Height/2-v)
|
||||
case tea.KeyMsg:
|
||||
if msg.Type == tea.KeyEnter {
|
||||
command := m.parent.sourceFields.Cmd()
|
||||
if m.parent.truffleFields.Cmd() != "" {
|
||||
command += " " + m.parent.truffleFields.Cmd()
|
||||
}
|
||||
cmd := func() tea.Msg { return SetArgsMsg(command) }
|
||||
return m, cmd
|
||||
}
|
||||
}
|
||||
if len(m.reviewListItems) > 0 && m.parent != nil && m.parent.sourceFields != nil {
|
||||
m.reviewListItems[0] = m.reviewListItems[0].(Item).SetDescription(m.parent.sourceFields.Summary())
|
||||
m.reviewListItems[1] = m.reviewListItems[1].(Item).SetDescription(m.parent.truffleFields.Summary())
|
||||
}
|
||||
var cmd tea.Cmd
|
||||
m.reviewList, cmd = m.reviewList.Update(msg)
|
||||
return m, tea.Batch(cmd)
|
||||
}
|
||||
|
||||
func (m *RunComponent) View() string {
|
||||
var view strings.Builder
|
||||
|
||||
view.WriteString("\n🔎 Source configuration\n")
|
||||
view.WriteString(m.parent.sourceFields.Summary())
|
||||
|
||||
view.WriteString("\n🐽 TruffleHog configuration\n")
|
||||
view.WriteString(m.parent.truffleFields.Summary())
|
||||
|
||||
view.WriteString("\n💸 Sales pitch\n")
|
||||
view.WriteString("\tContinuous monitoring, state tracking, remediations, and more\n")
|
||||
view.WriteString("\t🔗 https://trufflesecurity.com/trufflehog\n\n")
|
||||
|
||||
view.WriteString(styles.BoldTextStyle.Render("\n\n🐷 Run TruffleHog for "+m.parent.configTabSource) + " 🐷\n\n")
|
||||
|
||||
view.WriteString("Generated TruffleHog command\n")
|
||||
view.WriteString(styles.HintTextStyle.Render("Save this if you want to run it again later!") + "\n")
|
||||
|
||||
command := m.parent.sourceFields.Cmd()
|
||||
if m.parent.truffleFields.Cmd() != "" {
|
||||
command += " " + m.parent.truffleFields.Cmd()
|
||||
}
|
||||
view.WriteString(styles.CodeTextStyle.Render(command))
|
||||
|
||||
focusedStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
|
||||
view.WriteString("\n\n" + focusedStyle.Render("[ Run TruffleHog ]") + "\n\n")
|
||||
|
||||
// view.WriteString(m.reviewList.View())
|
||||
return view.String()
|
||||
}
|
||||
|
||||
func (m *RunComponent) ShortHelp() []key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RunComponent) FullHelp() [][]key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
71
pkg/tui/pages/source_configure/source_component.go
Normal file
71
pkg/tui/pages/source_configure/source_component.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package source_configure
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
|
||||
)
|
||||
|
||||
type SourceComponent struct {
|
||||
common.Common
|
||||
parent *SourceConfigure
|
||||
form tea.Model
|
||||
}
|
||||
|
||||
func NewSourceComponent(common common.Common, parent *SourceConfigure) *SourceComponent {
|
||||
return &SourceComponent{
|
||||
Common: common,
|
||||
parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SourceComponent) SetForm(form tea.Model) {
|
||||
m.form = form
|
||||
}
|
||||
|
||||
func (m *SourceComponent) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SourceComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// TODO: Add a focus variable.
|
||||
if m.form != nil {
|
||||
model, cmd := m.form.Update(msg)
|
||||
m.form = model
|
||||
return m, cmd
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *SourceComponent) View() string {
|
||||
var view strings.Builder
|
||||
|
||||
view.WriteString(styles.BoldTextStyle.Render("\nConfiguring "+styles.PrimaryTextStyle.Render(m.parent.configTabSource)) + "\n")
|
||||
|
||||
view.WriteString(styles.HintTextStyle.Render("* required field") + "\n\n")
|
||||
|
||||
sourceNote := sources.GetSourceNotes(m.parent.configTabSource)
|
||||
if len(sourceNote) > 0 {
|
||||
view.WriteString("⭐ " + sourceNote + " ⭐\n\n")
|
||||
}
|
||||
|
||||
if m.form != nil {
|
||||
view.WriteString(m.form.View())
|
||||
view.WriteString("\n")
|
||||
}
|
||||
return view.String()
|
||||
}
|
||||
|
||||
func (m *SourceComponent) ShortHelp() []key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SourceComponent) FullHelp() [][]key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
139
pkg/tui/pages/source_configure/source_configure.go
Normal file
139
pkg/tui/pages/source_configure/source_configure.go
Normal file
|
@ -0,0 +1,139 @@
|
|||
package source_configure
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/tabs"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources"
|
||||
)
|
||||
|
||||
type SetSourceMsg struct {
|
||||
Source string
|
||||
}
|
||||
|
||||
type tab int
|
||||
|
||||
const (
|
||||
configTab tab = iota
|
||||
truffleConfigTab
|
||||
runTab
|
||||
)
|
||||
|
||||
func (t tab) String() string {
|
||||
return []string{
|
||||
"1. Source Configuration",
|
||||
"2. TruffleHog Configuration",
|
||||
"3. Run",
|
||||
}[t]
|
||||
}
|
||||
|
||||
type SourceConfigure struct {
|
||||
common.Common
|
||||
activeTab tab
|
||||
tabs *tabs.Tabs
|
||||
configTabSource string
|
||||
tabComponents []common.Component
|
||||
sourceFields sources.CmdModel
|
||||
truffleFields sources.CmdModel
|
||||
}
|
||||
|
||||
func (m SourceConfigure) Init() tea.Cmd {
|
||||
return m.tabs.Init()
|
||||
}
|
||||
|
||||
func New(c common.Common) *SourceConfigure {
|
||||
conf := SourceConfigure{Common: c, truffleFields: GetTrufflehogConfiguration()}
|
||||
conf.tabs = tabs.New(c, []string{configTab.String(), truffleConfigTab.String(), runTab.String()})
|
||||
|
||||
conf.tabComponents = []common.Component{
|
||||
configTab: NewSourceComponent(c, &conf),
|
||||
truffleConfigTab: NewTrufflehogComponent(c, &conf),
|
||||
runTab: NewRunComponent(c, &conf),
|
||||
}
|
||||
return &conf
|
||||
}
|
||||
|
||||
func (m *SourceConfigure) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
for i := range m.tabComponents {
|
||||
model, cmd := m.tabComponents[i].Update(msg)
|
||||
m.tabComponents[i] = model.(common.Component)
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
case tabs.ActiveTabMsg:
|
||||
m.activeTab = tab(msg)
|
||||
t, cmd := m.tabs.Update(msg)
|
||||
m.tabs = t.(*tabs.Tabs)
|
||||
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
case tabs.SelectTabMsg:
|
||||
m.activeTab = tab(msg)
|
||||
t, cmd := m.tabs.Update(msg)
|
||||
m.tabs = t.(*tabs.Tabs)
|
||||
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
case tea.KeyMsg:
|
||||
t, cmd := m.tabs.Update(msg)
|
||||
m.tabs = t.(*tabs.Tabs)
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
case SetSourceMsg:
|
||||
m.configTabSource = msg.Source
|
||||
// TODO: Use actual messages or something?
|
||||
m.tabComponents[truffleConfigTab].(*TrufflehogComponent).SetForm(m.truffleFields)
|
||||
fields := sources.GetSourceFields(m.configTabSource)
|
||||
|
||||
if fields != nil {
|
||||
m.sourceFields = fields
|
||||
m.tabComponents[configTab].(*SourceComponent).SetForm(fields)
|
||||
}
|
||||
|
||||
case textinputs.SelectNextMsg, textinputs.SelectSkipMsg:
|
||||
if m.activeTab < runTab {
|
||||
m.activeTab++
|
||||
}
|
||||
t, cmd := m.tabs.Update(tabs.SelectTabMsg(int(m.activeTab)))
|
||||
m.tabs = t.(*tabs.Tabs)
|
||||
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
tab, cmd := m.tabComponents[m.activeTab].Update(msg)
|
||||
m.tabComponents[m.activeTab] = tab.(common.Component)
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m *SourceConfigure) View() string {
|
||||
return lipgloss.JoinVertical(lipgloss.Top,
|
||||
m.tabs.View(),
|
||||
m.tabComponents[m.activeTab].View(),
|
||||
)
|
||||
}
|
||||
|
||||
func (m *SourceConfigure) ShortHelp() []key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SourceConfigure) FullHelp() [][]key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
66
pkg/tui/pages/source_configure/trufflehog_component.go
Normal file
66
pkg/tui/pages/source_configure/trufflehog_component.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package source_configure
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
|
||||
)
|
||||
|
||||
type TrufflehogComponent struct {
|
||||
common.Common
|
||||
parent *SourceConfigure
|
||||
form tea.Model
|
||||
}
|
||||
|
||||
func NewTrufflehogComponent(common common.Common, parent *SourceConfigure) *TrufflehogComponent {
|
||||
return &TrufflehogComponent{
|
||||
Common: common,
|
||||
parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *TrufflehogComponent) SetForm(form tea.Model) {
|
||||
m.form = form
|
||||
}
|
||||
|
||||
func (m *TrufflehogComponent) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *TrufflehogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// TODO: Add a focus variable.
|
||||
if m.form != nil {
|
||||
model, cmd := m.form.Update(msg)
|
||||
m.form = model
|
||||
|
||||
return m, cmd
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *TrufflehogComponent) View() string {
|
||||
var view strings.Builder
|
||||
|
||||
view.WriteString(styles.BoldTextStyle.Render("\nConfiguring "+styles.PrimaryTextStyle.Render("TruffleHog")) + "\n")
|
||||
view.WriteString(styles.HintTextStyle.Render("You can skip this completely and run with defaults") + "\n\n")
|
||||
|
||||
if m.form != nil {
|
||||
view.WriteString(m.form.View())
|
||||
view.WriteString("\n")
|
||||
}
|
||||
|
||||
return view.String()
|
||||
}
|
||||
|
||||
func (m *TrufflehogComponent) ShortHelp() []key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *TrufflehogComponent) FullHelp() [][]key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
115
pkg/tui/pages/source_configure/trufflehog_configure.go
Normal file
115
pkg/tui/pages/source_configure/trufflehog_configure.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package source_configure
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
|
||||
)
|
||||
|
||||
type truffleCmdModel struct {
|
||||
textinputs.Model
|
||||
}
|
||||
|
||||
func GetTrufflehogConfiguration() truffleCmdModel {
|
||||
verification := textinputs.InputConfig{
|
||||
Label: "Skip Verification",
|
||||
Key: "no-verification",
|
||||
Required: false,
|
||||
Help: "Check if a suspected secret is real or not",
|
||||
Placeholder: "false",
|
||||
}
|
||||
|
||||
verifiedResults := textinputs.InputConfig{
|
||||
Label: "Verified results",
|
||||
Key: "only-verified",
|
||||
Required: false,
|
||||
Help: "Return only verified results",
|
||||
Placeholder: "false",
|
||||
}
|
||||
|
||||
jsonOutput := textinputs.InputConfig{
|
||||
Label: "JSON output",
|
||||
Key: "json",
|
||||
Required: false,
|
||||
Help: "Output results to JSON",
|
||||
Placeholder: "false",
|
||||
}
|
||||
|
||||
excludeDetectors := textinputs.InputConfig{
|
||||
Label: "Exclude detectors",
|
||||
Key: "exclude_detectors",
|
||||
Required: false,
|
||||
Help: "Comma separated list of detector types to exclude. Protobuf name or IDs may be used, as well as ranges. IDs defined here take precedence over the include list.",
|
||||
Placeholder: "",
|
||||
}
|
||||
|
||||
concurrency := textinputs.InputConfig{
|
||||
Label: "Concurrency",
|
||||
Key: "concurrency",
|
||||
Required: false,
|
||||
Help: "Number of concurrent workers.",
|
||||
Placeholder: "1",
|
||||
}
|
||||
|
||||
return truffleCmdModel{textinputs.New([]textinputs.InputConfig{jsonOutput, verification, verifiedResults, excludeDetectors, concurrency}).SetSkip(true)}
|
||||
}
|
||||
|
||||
func (m truffleCmdModel) Cmd() string {
|
||||
var command []string
|
||||
inputs := m.GetInputs()
|
||||
|
||||
if isTrue(inputs["json"]) {
|
||||
command = append(command, "--json")
|
||||
}
|
||||
|
||||
if isTrue(inputs["no-verification"]) {
|
||||
command = append(command, "--no-verification")
|
||||
}
|
||||
|
||||
if isTrue(inputs["only-verified"]) {
|
||||
command = append(command, "--only-verified")
|
||||
}
|
||||
|
||||
if inputs["exclude_detectors"] != "" {
|
||||
cmd := "--exclude-detectors=" + strings.ReplaceAll(inputs["exclude_detectors"], " ", "")
|
||||
command = append(command, cmd)
|
||||
}
|
||||
|
||||
if inputs["concurrency"] != "" {
|
||||
command = append(command, "--concurrency="+inputs["concurrency"])
|
||||
}
|
||||
|
||||
return strings.Join(command, " ")
|
||||
}
|
||||
|
||||
func (m truffleCmdModel) Summary() string {
|
||||
summary := strings.Builder{}
|
||||
keys := []string{"no-verification", "only-verified", "json", "exclude_detectors", "concurrency"}
|
||||
|
||||
inputs := m.GetInputs()
|
||||
labels := m.GetLabels()
|
||||
for _, key := range keys {
|
||||
if inputs[key] != "" {
|
||||
summary.WriteString("\t" + labels[key] + ": " + inputs[key] + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
if summary.Len() == 0 {
|
||||
summary.WriteString("\tRunning with defaults\n")
|
||||
|
||||
}
|
||||
|
||||
summary.WriteString("\n")
|
||||
return summary.String()
|
||||
}
|
||||
|
||||
func isTrue(val string) bool {
|
||||
value := strings.ToLower(val)
|
||||
isTrue, _ := strconv.ParseBool(value)
|
||||
|
||||
if isTrue || value == "yes" || value == "y" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
32
pkg/tui/pages/source_select/item.go
Normal file
32
pkg/tui/pages/source_select/item.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package source_select
|
||||
|
||||
type SourceItem struct {
|
||||
title string
|
||||
description string
|
||||
enterprise bool
|
||||
}
|
||||
|
||||
func OssItem(title, description string) SourceItem {
|
||||
return SourceItem{title, description, false}
|
||||
}
|
||||
|
||||
func EnterpriseItem(title, description string) SourceItem {
|
||||
return SourceItem{title, description, true}
|
||||
}
|
||||
|
||||
func (i SourceItem) ID() string { return i.title }
|
||||
|
||||
func (i SourceItem) Title() string {
|
||||
if i.enterprise {
|
||||
return "💸 " + i.title
|
||||
}
|
||||
return i.title
|
||||
}
|
||||
func (i SourceItem) Description() string {
|
||||
if i.enterprise {
|
||||
return i.description + " (Enterprise only)"
|
||||
}
|
||||
return i.description
|
||||
}
|
||||
|
||||
func (i SourceItem) FilterValue() string { return i.title + i.description }
|
222
pkg/tui/pages/source_select/source_select.go
Normal file
222
pkg/tui/pages/source_select/source_select.go
Normal file
|
@ -0,0 +1,222 @@
|
|||
package source_select
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/selector"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
|
||||
)
|
||||
|
||||
// TODO: Review light theme styling
|
||||
var (
|
||||
titleStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#FFFDF5")).
|
||||
Background(lipgloss.Color(styles.Colors["bronze"])).
|
||||
Padding(0, 1)
|
||||
|
||||
// FIXME: Hon pls help
|
||||
errorStatusMessageStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Dark: "#ff0000"}).
|
||||
Render
|
||||
|
||||
selectedSourceItemStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.NormalBorder(), false, false, false, true).
|
||||
BorderForeground(lipgloss.AdaptiveColor{Dark: styles.Colors["sprout"], Light: styles.Colors["bronze"]}).
|
||||
Foreground(lipgloss.AdaptiveColor{Dark: styles.Colors["sprout"], Light: styles.Colors["fern"]}).
|
||||
Padding(0, 0, 0, 1)
|
||||
|
||||
selectedDescription = selectedSourceItemStyle.Copy().
|
||||
Foreground(lipgloss.AdaptiveColor{Dark: styles.Colors["sprout"], Light: styles.Colors["sprout"]})
|
||||
)
|
||||
|
||||
type listKeyMap struct {
|
||||
toggleHelpMenu key.Binding
|
||||
}
|
||||
|
||||
type (
|
||||
SourceSelect struct {
|
||||
common.Common
|
||||
sourcesList list.Model
|
||||
keys *listKeyMap
|
||||
delegateKeys *delegateKeyMap
|
||||
selector *selector.Selector
|
||||
}
|
||||
)
|
||||
|
||||
func New(c common.Common) *SourceSelect {
|
||||
var (
|
||||
delegateKeys = newDelegateKeyMap()
|
||||
listKeys = &listKeyMap{
|
||||
toggleHelpMenu: key.NewBinding(
|
||||
key.WithKeys("H"),
|
||||
key.WithHelp("H", "toggle help"),
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
// Make list of SourceItems.
|
||||
SourceItems := []list.Item{
|
||||
// Open source sources.
|
||||
OssItem("Git", "Scan git repositories."),
|
||||
OssItem("GitHub", "Scan GitHub repositories and/or organizations."),
|
||||
OssItem("GitLab", "Scan GitLab repositories."),
|
||||
OssItem("Filesystem", "Scan your filesystem by selecting what directories to scan."),
|
||||
OssItem("AWS S3", "Scan Amazon S3 buckets."),
|
||||
OssItem("CircleCI", "Scan CircleCI, a CI/CD platform."),
|
||||
OssItem("Syslog", "Scan syslog, event data logs."),
|
||||
OssItem("Docker", "Scan a Docker instance, a containerized application."),
|
||||
OssItem("GCS (Google Cloud Storage)", "Scan a Google Cloud Storage instance."),
|
||||
// Enterprise sources.
|
||||
EnterpriseItem("Artifactory", "Scan JFrog Artifactory packages."),
|
||||
EnterpriseItem("BitBucket", "Scan Atlassian's Git-based source code repository hosting service."),
|
||||
EnterpriseItem("Buildkite", "Scan Buildkite, a CI/CD platform."),
|
||||
EnterpriseItem("Confluence", "Scan Atlassian's web-based wiki and knowledge base."),
|
||||
EnterpriseItem("Gerrit", "Scan Gerrit, a code collaboration tool"),
|
||||
EnterpriseItem("Jenkins ", "Scan Jenkins, a CI/CD platform."),
|
||||
EnterpriseItem("Jira", "Scan Atlassian's issue & project tracking software."),
|
||||
EnterpriseItem("Slack", "Scan Slack, a messaging and communication platform."),
|
||||
EnterpriseItem("Microsoft Teams", "Scan Microsoft Teams, a messaging and communication platform."),
|
||||
EnterpriseItem("Microsoft Sharepoint", "Scan Microsoft Sharepoint, a collaboration and document management platform."),
|
||||
EnterpriseItem("Google Drive", "Scan Google Drive, a cloud-based storage and file sync service."),
|
||||
}
|
||||
|
||||
// Setup list
|
||||
delegate := newSourceItemDelegate(delegateKeys)
|
||||
delegate.Styles.SelectedTitle = selectedSourceItemStyle
|
||||
delegate.Styles.SelectedDesc = selectedDescription
|
||||
|
||||
sourcesList := list.New(SourceItems, delegate, 0, 0)
|
||||
sourcesList.Title = "Sources"
|
||||
sourcesList.Styles.Title = titleStyle
|
||||
sourcesList.StatusMessageLifetime = 10 * time.Second
|
||||
|
||||
sourcesList.AdditionalFullHelpKeys = func() []key.Binding {
|
||||
return []key.Binding{
|
||||
listKeys.toggleHelpMenu,
|
||||
}
|
||||
}
|
||||
|
||||
sourcesList.SetShowStatusBar(false)
|
||||
sel := selector.New(c, []selector.IdentifiableItem{}, delegate)
|
||||
|
||||
return &SourceSelect{
|
||||
Common: c,
|
||||
sourcesList: sourcesList,
|
||||
keys: listKeys,
|
||||
delegateKeys: delegateKeys,
|
||||
selector: sel,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SourceSelect) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SourceSelect) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
h, v := styles.AppStyle.GetFrameSize()
|
||||
m.sourcesList.SetSize(msg.Width-h, msg.Height-v)
|
||||
|
||||
case tea.KeyMsg:
|
||||
// Don't match any of the keys below if we're actively filtering.
|
||||
if m.sourcesList.FilterState() == list.Filtering {
|
||||
break
|
||||
}
|
||||
|
||||
switch {
|
||||
case key.Matches(msg, m.keys.toggleHelpMenu):
|
||||
m.sourcesList.SetShowHelp(!m.sourcesList.ShowHelp())
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
// This will also call our delegate's update function.
|
||||
newListModel, cmd := m.sourcesList.Update(msg)
|
||||
m.sourcesList = newListModel
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
if m.selector != nil {
|
||||
sel, cmd := m.selector.Update(msg)
|
||||
m.selector = sel.(*selector.Selector)
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m *SourceSelect) View() string {
|
||||
return styles.AppStyle.Render(m.sourcesList.View())
|
||||
}
|
||||
|
||||
func (m *SourceSelect) ShortHelp() []key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SourceSelect) FullHelp() [][]key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
||||
|
||||
func newSourceItemDelegate(keys *delegateKeyMap) list.DefaultDelegate {
|
||||
d := list.NewDefaultDelegate()
|
||||
|
||||
d.UpdateFunc = func(msg tea.Msg, m *list.Model) tea.Cmd {
|
||||
selectedSourceItem, ok := m.SelectedItem().(SourceItem)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if msg, ok := msg.(tea.KeyMsg); ok && key.Matches(msg, keys.choose) {
|
||||
if selectedSourceItem.enterprise {
|
||||
return m.NewStatusMessage(errorStatusMessageStyle(
|
||||
"That's an enterprise only source. Learn more at trufflesecurity.com",
|
||||
))
|
||||
}
|
||||
|
||||
return func() tea.Msg {
|
||||
return selector.SelectMsg{IdentifiableItem: selectedSourceItem}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
help := []key.Binding{keys.choose}
|
||||
d.ShortHelpFunc = func() []key.Binding { return help }
|
||||
d.FullHelpFunc = func() [][]key.Binding { return [][]key.Binding{help} }
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
type delegateKeyMap struct {
|
||||
choose key.Binding
|
||||
}
|
||||
|
||||
// Additional short help entries. This satisfies the help.KeyMap interface and
|
||||
// is entirely optional.
|
||||
func (d delegateKeyMap) ShortHelp() []key.Binding {
|
||||
return []key.Binding{d.choose}
|
||||
}
|
||||
|
||||
// Additional full help entries. This satisfies the help.KeyMap interface and
|
||||
// is entirely optional.
|
||||
func (d delegateKeyMap) FullHelp() [][]key.Binding {
|
||||
return [][]key.Binding{{d.choose}}
|
||||
}
|
||||
|
||||
func newDelegateKeyMap() *delegateKeyMap {
|
||||
return &delegateKeyMap{
|
||||
choose: key.NewBinding(
|
||||
key.WithKeys("enter"),
|
||||
key.WithHelp("enter", "choose"),
|
||||
),
|
||||
}
|
||||
}
|
59
pkg/tui/pages/view_oss/view_oss.go
Normal file
59
pkg/tui/pages/view_oss/view_oss.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package view_oss
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
|
||||
)
|
||||
|
||||
type ViewOSS struct {
|
||||
common.Common
|
||||
viewed bool
|
||||
}
|
||||
|
||||
var (
|
||||
linkStyle = lipgloss.NewStyle().Foreground(
|
||||
lipgloss.Color("28")) // green
|
||||
)
|
||||
|
||||
func New(c common.Common) *ViewOSS {
|
||||
return &ViewOSS{
|
||||
Common: c,
|
||||
viewed: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ViewOSS) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ViewOSS) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.viewed {
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
return m, func() tea.Msg { return nil }
|
||||
}
|
||||
|
||||
func (m *ViewOSS) View() string {
|
||||
s := strings.Builder{}
|
||||
s.WriteString("View our open-source project on GitHub\n")
|
||||
s.WriteString(linkStyle.Render("🔗 https://github.com/trufflesecurity/trufflehog "))
|
||||
|
||||
m.viewed = true
|
||||
return styles.AppStyle.Render(s.String())
|
||||
}
|
||||
|
||||
func (m *ViewOSS) ShortHelp() []key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ViewOSS) FullHelp() [][]key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
137
pkg/tui/pages/wizard_intro/item.go
Normal file
137
pkg/tui/pages/wizard_intro/item.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package wizard_intro
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
)
|
||||
|
||||
// Item represents a single item in the selector.
|
||||
type Item int
|
||||
|
||||
// ID implements selector.IdentifiableItem.
|
||||
func (i Item) ID() string {
|
||||
return i.String()
|
||||
}
|
||||
|
||||
// Title returns the item title. Implements list.DefaultItem.
|
||||
func (i Item) Title() string { return i.String() }
|
||||
|
||||
// Description returns the item description. Implements list.DefaultItem.
|
||||
func (i Item) Description() string { return "" }
|
||||
|
||||
// FilterValue implements list.Item.
|
||||
func (i Item) FilterValue() string { return i.Title() }
|
||||
|
||||
// Command returns the item Command view.
|
||||
func (i Item) Command() string {
|
||||
return i.Title()
|
||||
}
|
||||
|
||||
// ItemDelegate is the delegate for the item.
|
||||
type ItemDelegate struct {
|
||||
common *common.Common
|
||||
}
|
||||
|
||||
// Width returns the item width.
|
||||
func (d ItemDelegate) Width() int {
|
||||
width := d.common.Styles.MenuItem.GetHorizontalFrameSize() + d.common.Styles.MenuItem.GetWidth()
|
||||
return width
|
||||
}
|
||||
|
||||
// Height returns the item height. Implements list.ItemDelegate.
|
||||
func (d ItemDelegate) Height() int {
|
||||
height := d.common.Styles.MenuItem.GetVerticalFrameSize() + d.common.Styles.MenuItem.GetHeight()
|
||||
return height
|
||||
}
|
||||
|
||||
// Spacing returns the spacing between items. Implements list.ItemDelegate.
|
||||
func (d ItemDelegate) Spacing() int { return 1 }
|
||||
|
||||
// Update implements list.ItemDelegate.
|
||||
func (d ItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd {
|
||||
idx := m.Index()
|
||||
item, ok := m.SelectedItem().(Item)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, d.common.KeyMap.Copy):
|
||||
d.common.Copy.Copy(item.Command())
|
||||
return m.SetItem(idx, item)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Render implements list.ItemDelegate.
|
||||
func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
|
||||
i := listItem.(Item)
|
||||
s := strings.Builder{}
|
||||
var matchedRunes []int
|
||||
|
||||
// Conditions
|
||||
var (
|
||||
isSelected = index == m.Index()
|
||||
isFiltered = m.FilterState() == list.Filtering || m.FilterState() == list.FilterApplied
|
||||
)
|
||||
|
||||
styles := d.common.Styles.RepoSelector.Normal
|
||||
if isSelected {
|
||||
styles = d.common.Styles.RepoSelector.Active
|
||||
}
|
||||
|
||||
title := i.Title()
|
||||
title = common.TruncateString(title, m.Width()-styles.Base.GetHorizontalFrameSize())
|
||||
// if i.repo.IsPrivate() {
|
||||
// title += " 🔒"
|
||||
// }
|
||||
if isSelected {
|
||||
title += " "
|
||||
}
|
||||
updatedStr := " Updated"
|
||||
if m.Width()-styles.Base.GetHorizontalFrameSize()-lipgloss.Width(updatedStr)-lipgloss.Width(title) <= 0 {
|
||||
updatedStr = ""
|
||||
}
|
||||
updatedStyle := styles.Updated.Copy().
|
||||
Align(lipgloss.Right).
|
||||
Width(m.Width() - styles.Base.GetHorizontalFrameSize() - lipgloss.Width(title))
|
||||
updated := updatedStyle.Render(updatedStr)
|
||||
|
||||
if isFiltered && index < len(m.VisibleItems()) {
|
||||
// Get indices of matched characters
|
||||
matchedRunes = m.MatchesForItem(index)
|
||||
}
|
||||
|
||||
if isFiltered {
|
||||
unmatched := styles.Title.Copy().Inline(true)
|
||||
matched := unmatched.Copy().Underline(true)
|
||||
title = lipgloss.StyleRunes(title, matchedRunes, matched, unmatched)
|
||||
}
|
||||
title = styles.Title.Render(title)
|
||||
desc := i.Description()
|
||||
desc = common.TruncateString(desc, m.Width()-styles.Base.GetHorizontalFrameSize())
|
||||
desc = styles.Desc.Render(desc)
|
||||
|
||||
s.WriteString(lipgloss.JoinHorizontal(lipgloss.Bottom, title, updated))
|
||||
s.WriteRune('\n')
|
||||
s.WriteString(desc)
|
||||
s.WriteRune('\n')
|
||||
cmd := common.TruncateString(i.Command(), m.Width()-styles.Base.GetHorizontalFrameSize())
|
||||
cmd = styles.Command.Render(cmd)
|
||||
|
||||
s.WriteString(cmd)
|
||||
fmt.Fprint(w,
|
||||
d.common.Zone.Mark(i.ID(),
|
||||
styles.Base.Render(s.String()),
|
||||
),
|
||||
)
|
||||
}
|
109
pkg/tui/pages/wizard_intro/wizard_intro.go
Normal file
109
pkg/tui/pages/wizard_intro/wizard_intro.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
package wizard_intro
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/selector"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
|
||||
)
|
||||
|
||||
const (
|
||||
ScanSourceWithWizard Item = iota
|
||||
// ScanSourceWithConfig
|
||||
ViewHelpDocs
|
||||
ViewOSSProject
|
||||
EnterpriseInquire
|
||||
Quit
|
||||
)
|
||||
|
||||
func (w Item) String() string {
|
||||
switch w {
|
||||
case ScanSourceWithWizard:
|
||||
return "Scan a source using wizard"
|
||||
//case ScanSourceWithConfig:
|
||||
// return "Scan a source with a config file"
|
||||
case ViewHelpDocs:
|
||||
return "View help docs"
|
||||
case ViewOSSProject:
|
||||
return "View open-source project"
|
||||
case EnterpriseInquire:
|
||||
return "Inquire about TruffleHog Enterprise"
|
||||
case Quit:
|
||||
return "Quit"
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
type WizardIntro struct {
|
||||
common.Common
|
||||
selector *selector.Selector
|
||||
}
|
||||
|
||||
func New(cmn common.Common) *WizardIntro {
|
||||
sel := selector.New(cmn,
|
||||
[]selector.IdentifiableItem{
|
||||
ScanSourceWithWizard,
|
||||
// ScanSourceWithConfig,
|
||||
ViewHelpDocs,
|
||||
ViewOSSProject,
|
||||
EnterpriseInquire,
|
||||
Quit,
|
||||
},
|
||||
ItemDelegate{&cmn})
|
||||
|
||||
return &WizardIntro{Common: cmn, selector: sel}
|
||||
}
|
||||
|
||||
func (m *WizardIntro) Init() tea.Cmd {
|
||||
m.selector.Select(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *WizardIntro) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
cmds := make([]tea.Cmd, 0)
|
||||
|
||||
s, cmd := m.selector.Update(msg)
|
||||
m.selector = s.(*selector.Selector)
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m *WizardIntro) View() string {
|
||||
s := strings.Builder{}
|
||||
s.WriteString("What do you want to do?\n\n")
|
||||
|
||||
for i, selectorItem := range m.selector.Items() {
|
||||
// Cast the interface to the concrete Item struct.
|
||||
item := selectorItem.(Item)
|
||||
if m.selector.Index() == i {
|
||||
selectedStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(styles.Colors["sprout"]))
|
||||
s.WriteString(selectedStyle.Render(" (•) " + item.Title()))
|
||||
} else {
|
||||
s.WriteString(" ( ) " + item.Title())
|
||||
}
|
||||
s.WriteString("\n")
|
||||
}
|
||||
|
||||
return styles.AppStyle.Render(s.String())
|
||||
}
|
||||
|
||||
func (m *WizardIntro) ShortHelp() []key.Binding {
|
||||
kb := make([]key.Binding, 0)
|
||||
kb = append(kb,
|
||||
m.Common.KeyMap.UpDown,
|
||||
m.Common.KeyMap.Section,
|
||||
)
|
||||
return kb
|
||||
}
|
||||
|
||||
func (m *WizardIntro) FullHelp() [][]key.Binding {
|
||||
// TODO: actually return something
|
||||
return nil
|
||||
}
|
44
pkg/tui/sources/circleci/circleci.go
Normal file
44
pkg/tui/sources/circleci/circleci.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package circleci
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
|
||||
)
|
||||
|
||||
type circleCiCmdModel struct {
|
||||
textinputs.Model
|
||||
}
|
||||
|
||||
func GetFields() circleCiCmdModel {
|
||||
token := textinputs.InputConfig{
|
||||
Label: "API Token",
|
||||
Key: "token",
|
||||
Required: true,
|
||||
Placeholder: "top secret token",
|
||||
}
|
||||
|
||||
return circleCiCmdModel{textinputs.New([]textinputs.InputConfig{token})}
|
||||
}
|
||||
|
||||
func (m circleCiCmdModel) Cmd() string {
|
||||
var command []string
|
||||
command = append(command, "trufflehog", "circleci")
|
||||
|
||||
inputs := m.GetInputs()
|
||||
|
||||
if inputs["token"] != "" {
|
||||
command = append(command, "--token="+inputs["token"])
|
||||
}
|
||||
|
||||
return strings.Join(command, " ")
|
||||
}
|
||||
|
||||
func (m circleCiCmdModel) Summary() string {
|
||||
inputs := m.GetInputs()
|
||||
labels := m.GetLabels()
|
||||
keys := []string{"token"}
|
||||
|
||||
return common.SummarizeSource(keys, inputs, labels)
|
||||
}
|
49
pkg/tui/sources/docker/docker.go
Normal file
49
pkg/tui/sources/docker/docker.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
|
||||
)
|
||||
|
||||
type dockerCmdModel struct {
|
||||
textinputs.Model
|
||||
}
|
||||
|
||||
func GetFields() dockerCmdModel {
|
||||
images := textinputs.InputConfig{
|
||||
Label: "Docker image(s)",
|
||||
Key: "images",
|
||||
Required: true,
|
||||
Help: "Separate by space if multiple.",
|
||||
Placeholder: "trufflesecurity/secrets",
|
||||
}
|
||||
|
||||
return dockerCmdModel{textinputs.New([]textinputs.InputConfig{images})}
|
||||
}
|
||||
|
||||
func (m dockerCmdModel) Cmd() string {
|
||||
|
||||
var command []string
|
||||
command = append(command, "trufflehog", "docker")
|
||||
|
||||
inputs := m.GetInputs()
|
||||
vals := inputs["images"]
|
||||
if vals != "" {
|
||||
images := strings.Fields(vals)
|
||||
for _, image := range images {
|
||||
command = append(command, "--image="+image)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(command, " ")
|
||||
}
|
||||
|
||||
func (m dockerCmdModel) Summary() string {
|
||||
inputs := m.GetInputs()
|
||||
labels := m.GetLabels()
|
||||
keys := []string{"images"}
|
||||
|
||||
return common.SummarizeSource(keys, inputs, labels)
|
||||
}
|
45
pkg/tui/sources/filesystem/filesystem.go
Normal file
45
pkg/tui/sources/filesystem/filesystem.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package filesystem
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
|
||||
)
|
||||
|
||||
type fsModel struct {
|
||||
textinputs.Model
|
||||
}
|
||||
|
||||
func GetFields() fsModel {
|
||||
path := textinputs.InputConfig{
|
||||
Label: "Path",
|
||||
Key: "path",
|
||||
Required: true,
|
||||
Help: "Files and directories to scan. Separate by space if multiple.",
|
||||
Placeholder: "path/to/file.txt path/to/another/dir",
|
||||
}
|
||||
|
||||
return fsModel{textinputs.New([]textinputs.InputConfig{path})}
|
||||
}
|
||||
|
||||
func (m fsModel) Cmd() string {
|
||||
var command []string
|
||||
command = append(command, "trufflehog", "filesystem")
|
||||
|
||||
inputs := m.GetInputs()
|
||||
|
||||
if inputs["path"] != "" {
|
||||
command = append(command, inputs["path"])
|
||||
}
|
||||
|
||||
return strings.Join(command, " ")
|
||||
}
|
||||
|
||||
func (m fsModel) Summary() string {
|
||||
inputs := m.GetInputs()
|
||||
labels := m.GetLabels()
|
||||
|
||||
keys := []string{"path"}
|
||||
return common.SummarizeSource(keys, inputs, labels)
|
||||
}
|
44
pkg/tui/sources/gcs/gcs.go
Normal file
44
pkg/tui/sources/gcs/gcs.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package gcs
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
|
||||
)
|
||||
|
||||
type gcsCmdModel struct {
|
||||
textinputs.Model
|
||||
}
|
||||
|
||||
func GetFields() gcsCmdModel {
|
||||
projectId := textinputs.InputConfig{
|
||||
Label: "Project ID",
|
||||
Key: "project_id",
|
||||
Required: true,
|
||||
Placeholder: "my-project",
|
||||
}
|
||||
|
||||
return gcsCmdModel{textinputs.New([]textinputs.InputConfig{projectId})}
|
||||
}
|
||||
|
||||
func (m gcsCmdModel) Cmd() string {
|
||||
var command []string
|
||||
command = append(command, "trufflehog", "gcs")
|
||||
|
||||
inputs := m.GetInputs()
|
||||
if inputs["project_id"] != "" {
|
||||
command = append(command, "--project_id="+inputs["project_id"])
|
||||
}
|
||||
|
||||
command = append(command, "--cloud-environment")
|
||||
return strings.Join(command, " ")
|
||||
}
|
||||
|
||||
func (m gcsCmdModel) Summary() string {
|
||||
inputs := m.GetInputs()
|
||||
labels := m.GetLabels()
|
||||
|
||||
keys := []string{"project_id"}
|
||||
return common.SummarizeSource(keys, inputs, labels)
|
||||
}
|
44
pkg/tui/sources/git/git.go
Normal file
44
pkg/tui/sources/git/git.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
|
||||
)
|
||||
|
||||
type gitCmdModel struct {
|
||||
textinputs.Model
|
||||
}
|
||||
|
||||
func GetFields() gitCmdModel {
|
||||
uri := textinputs.InputConfig{
|
||||
Label: "Git URI",
|
||||
Key: "uri",
|
||||
Required: true,
|
||||
Placeholder: "git@github.com:trufflesecurity/trufflehog.git.",
|
||||
}
|
||||
|
||||
return gitCmdModel{textinputs.New([]textinputs.InputConfig{uri})}
|
||||
}
|
||||
|
||||
func (m gitCmdModel) Cmd() string {
|
||||
var command []string
|
||||
command = append(command, "trufflehog", "git")
|
||||
|
||||
inputs := m.GetInputs()
|
||||
|
||||
if inputs["uri"] != "" {
|
||||
command = append(command, inputs["uri"])
|
||||
}
|
||||
|
||||
return strings.Join(command, " ")
|
||||
}
|
||||
|
||||
func (m gitCmdModel) Summary() string {
|
||||
inputs := m.GetInputs()
|
||||
labels := m.GetLabels()
|
||||
|
||||
keys := []string{"uri"}
|
||||
return common.SummarizeSource(keys, inputs, labels)
|
||||
}
|
61
pkg/tui/sources/github/github.go
Normal file
61
pkg/tui/sources/github/github.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package github
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
|
||||
)
|
||||
|
||||
type githubCmdModel struct {
|
||||
textinputs.Model
|
||||
}
|
||||
|
||||
func GetNote() string {
|
||||
return "Please enter an organization OR repository."
|
||||
}
|
||||
|
||||
func GetFields() githubCmdModel {
|
||||
org := textinputs.InputConfig{
|
||||
Label: "Organization",
|
||||
Key: "org",
|
||||
Required: false,
|
||||
Help: "GitHub organization to scan.",
|
||||
Placeholder: "https://github.com/trufflesecurity",
|
||||
}
|
||||
|
||||
repo := textinputs.InputConfig{
|
||||
Label: "Repository",
|
||||
Key: "repo",
|
||||
Required: false,
|
||||
Help: "GitHub repo to scan.",
|
||||
Placeholder: "https://github.com/trufflesecurity/test_keys",
|
||||
}
|
||||
|
||||
return githubCmdModel{textinputs.New([]textinputs.InputConfig{org, repo})}
|
||||
}
|
||||
|
||||
func (m githubCmdModel) Cmd() string {
|
||||
var command []string
|
||||
command = append(command, "trufflehog", "github")
|
||||
|
||||
inputs := m.GetInputs()
|
||||
|
||||
if inputs["org"] != "" {
|
||||
command = append(command, "--org="+inputs["org"])
|
||||
}
|
||||
|
||||
if inputs["repo"] != "" {
|
||||
command = append(command, "--repo="+inputs["repo"])
|
||||
}
|
||||
|
||||
return strings.Join(command, " ")
|
||||
}
|
||||
|
||||
func (m githubCmdModel) Summary() string {
|
||||
inputs := m.GetInputs()
|
||||
labels := m.GetLabels()
|
||||
|
||||
keys := []string{"org", "repo"}
|
||||
return common.SummarizeSource(keys, inputs, labels)
|
||||
}
|
45
pkg/tui/sources/gitlab/gitlab.go
Normal file
45
pkg/tui/sources/gitlab/gitlab.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package gitlab
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
|
||||
)
|
||||
|
||||
type gitlabCmdModel struct {
|
||||
textinputs.Model
|
||||
}
|
||||
|
||||
func GetFields() gitlabCmdModel {
|
||||
token := textinputs.InputConfig{
|
||||
Label: "GitLab token",
|
||||
Key: "token",
|
||||
Required: true,
|
||||
Help: "Personal access token with read access",
|
||||
Placeholder: "glpat-",
|
||||
}
|
||||
|
||||
return gitlabCmdModel{textinputs.New([]textinputs.InputConfig{token})}
|
||||
}
|
||||
|
||||
func (m gitlabCmdModel) Cmd() string {
|
||||
var command []string
|
||||
command = append(command, "trufflehog", "gitlab")
|
||||
|
||||
inputs := m.GetInputs()
|
||||
|
||||
if inputs["token"] != "" {
|
||||
command = append(command, "--token="+inputs["token"])
|
||||
}
|
||||
|
||||
return strings.Join(command, " ")
|
||||
}
|
||||
|
||||
func (m gitlabCmdModel) Summary() string {
|
||||
inputs := m.GetInputs()
|
||||
labels := m.GetLabels()
|
||||
|
||||
keys := []string{"token"}
|
||||
return common.SummarizeSource(keys, inputs, labels)
|
||||
}
|
48
pkg/tui/sources/s3/s3.go
Normal file
48
pkg/tui/sources/s3/s3.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package s3
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
|
||||
)
|
||||
|
||||
type s3CmdModel struct {
|
||||
textinputs.Model
|
||||
}
|
||||
|
||||
func GetFields() s3CmdModel {
|
||||
bucket := textinputs.InputConfig{
|
||||
Label: "S3 bucket name(s)",
|
||||
Key: "buckets",
|
||||
Required: true,
|
||||
Placeholder: "my-bucket-name",
|
||||
Help: "Buckets to scan. Separate by space if multiple.",
|
||||
}
|
||||
|
||||
return s3CmdModel{textinputs.New([]textinputs.InputConfig{bucket})}
|
||||
}
|
||||
|
||||
func (m s3CmdModel) Cmd() string {
|
||||
var command []string
|
||||
command = append(command, "trufflehog", "s3")
|
||||
|
||||
inputs := m.GetInputs()
|
||||
vals := inputs["buckets"]
|
||||
if vals != "" {
|
||||
buckets := strings.Fields(vals)
|
||||
for _, bucket := range buckets {
|
||||
command = append(command, "--bucket="+bucket)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(command, " ")
|
||||
}
|
||||
|
||||
func (m s3CmdModel) Summary() string {
|
||||
inputs := m.GetInputs()
|
||||
labels := m.GetLabels()
|
||||
|
||||
keys := []string{"buckets"}
|
||||
return common.SummarizeSource(keys, inputs, labels)
|
||||
}
|
60
pkg/tui/sources/sources.go
Normal file
60
pkg/tui/sources/sources.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package sources
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/circleci"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/docker"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/filesystem"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/gcs"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/git"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/github"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/gitlab"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/s3"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/syslog"
|
||||
)
|
||||
|
||||
func GetSourceNotes(sourceName string) string {
|
||||
source := strings.ToLower(sourceName)
|
||||
switch source {
|
||||
case "github":
|
||||
return github.GetNote()
|
||||
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
type CmdModel interface {
|
||||
tea.Model
|
||||
Cmd() string
|
||||
Summary() string
|
||||
}
|
||||
|
||||
func GetSourceFields(sourceName string) CmdModel {
|
||||
source := strings.ToLower(sourceName)
|
||||
|
||||
switch source {
|
||||
case "git":
|
||||
return git.GetFields()
|
||||
case "github":
|
||||
return github.GetFields()
|
||||
case "gitlab":
|
||||
return gitlab.GetFields()
|
||||
case "filesystem":
|
||||
return filesystem.GetFields()
|
||||
case "aws s3":
|
||||
return s3.GetFields()
|
||||
case "gcs (google cloud storage)":
|
||||
return gcs.GetFields()
|
||||
case "syslog":
|
||||
return syslog.GetFields()
|
||||
case "circleci":
|
||||
return circleci.GetFields()
|
||||
case "docker":
|
||||
return docker.GetFields()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
82
pkg/tui/sources/syslog/syslog.go
Normal file
82
pkg/tui/sources/syslog/syslog.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package syslog
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
|
||||
)
|
||||
|
||||
type syslogCmdModel struct {
|
||||
textinputs.Model
|
||||
}
|
||||
|
||||
// TODO: review fields
|
||||
func GetFields() syslogCmdModel {
|
||||
protocol := textinputs.InputConfig{
|
||||
Label: "Protocol",
|
||||
Key: "protocol",
|
||||
Required: true,
|
||||
Help: "udp or tcp",
|
||||
Placeholder: "tcp",
|
||||
}
|
||||
|
||||
listenAddress := textinputs.InputConfig{
|
||||
Label: "Address",
|
||||
Key: "address",
|
||||
Help: "Address and port to listen on for syslog",
|
||||
Required: true,
|
||||
Placeholder: "127.0.0.1:514",
|
||||
}
|
||||
|
||||
tlsCert := textinputs.InputConfig{
|
||||
Label: "TLS Certificate",
|
||||
Key: "cert",
|
||||
Required: true,
|
||||
Help: "Path to TLS certificate",
|
||||
Placeholder: "/path/to/cert",
|
||||
}
|
||||
|
||||
tlsKey := textinputs.InputConfig{
|
||||
Label: "TLS Key",
|
||||
Key: "key",
|
||||
Required: true,
|
||||
Help: "Path to TLS key",
|
||||
Placeholder: "/path/to/key",
|
||||
}
|
||||
|
||||
format := textinputs.InputConfig{
|
||||
Label: "Log format",
|
||||
Key: "format",
|
||||
Required: true,
|
||||
Help: "Can be rfc3164 or rfc5424",
|
||||
Placeholder: "rfc3164",
|
||||
}
|
||||
|
||||
return syslogCmdModel{textinputs.New([]textinputs.InputConfig{listenAddress, protocol, tlsCert, tlsKey, format})}
|
||||
}
|
||||
|
||||
func (m syslogCmdModel) Cmd() string {
|
||||
var command []string
|
||||
command = append(command, "trufflehog", "syslog")
|
||||
|
||||
inputs := m.GetInputs()
|
||||
syslogKeys := [5]string{"address", "protocol", "cert", "key", "format"}
|
||||
|
||||
for _, key := range syslogKeys {
|
||||
if inputs[key] != "" {
|
||||
flag := "--" + key + "=" + inputs[key]
|
||||
command = append(command, flag)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(command, " ")
|
||||
}
|
||||
|
||||
func (m syslogCmdModel) Summary() string {
|
||||
inputs := m.GetInputs()
|
||||
labels := m.GetLabels()
|
||||
keys := []string{"address", "protocol", "cert", "key", "format"}
|
||||
|
||||
return common.SummarizeSource(keys, inputs, labels)
|
||||
}
|
493
pkg/tui/styles/styles.go
Normal file
493
pkg/tui/styles/styles.go
Normal file
|
@ -0,0 +1,493 @@
|
|||
package styles
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// XXX: For now, this is in its own package so that it can be shared between
|
||||
// different packages without incurring an illegal import cycle.
|
||||
|
||||
// https://github.com/charmbracelet/lipgloss#colors
|
||||
var Colors = map[string]string{
|
||||
"softblack": "#1e1e1e",
|
||||
"charcoal": "#252525",
|
||||
"stone": "#5a5a5a",
|
||||
"smoke": "#999999",
|
||||
"sand": "#e1deda",
|
||||
"cloud": "#f4efe9",
|
||||
"offwhite": "#faf8f7",
|
||||
"fern": "#38645a",
|
||||
"sprout": "#5bb381",
|
||||
"gold": "#ae8c57",
|
||||
"bronze": "#89553d",
|
||||
"coral": "#c15750",
|
||||
"violet": "#6b5b9a",
|
||||
}
|
||||
|
||||
var (
|
||||
BoldTextStyle = lipgloss.NewStyle().Bold(true)
|
||||
|
||||
PrimaryTextStyle = lipgloss.NewStyle().Foreground(
|
||||
lipgloss.Color("28")) // green
|
||||
|
||||
HintTextStyle = lipgloss.NewStyle().Foreground(
|
||||
lipgloss.Color("8")) // grey
|
||||
|
||||
CodeTextStyle = lipgloss.NewStyle().Background(lipgloss.Color("130")).Foreground(lipgloss.Color("15"))
|
||||
)
|
||||
|
||||
var AppStyle = lipgloss.NewStyle().Padding(1, 2)
|
||||
|
||||
// Styles defines styles for the UI.
|
||||
type Styles struct {
|
||||
ActiveBorderColor lipgloss.Color
|
||||
InactiveBorderColor lipgloss.Color
|
||||
|
||||
App lipgloss.Style
|
||||
ServerName lipgloss.Style
|
||||
TopLevelNormalTab lipgloss.Style
|
||||
TopLevelActiveTab lipgloss.Style
|
||||
TopLevelActiveTabDot lipgloss.Style
|
||||
|
||||
MenuItem lipgloss.Style
|
||||
MenuLastUpdate lipgloss.Style
|
||||
|
||||
RepoSelector struct {
|
||||
Normal struct {
|
||||
Base lipgloss.Style
|
||||
Title lipgloss.Style
|
||||
Desc lipgloss.Style
|
||||
Command lipgloss.Style
|
||||
Updated lipgloss.Style
|
||||
}
|
||||
Active struct {
|
||||
Base lipgloss.Style
|
||||
Title lipgloss.Style
|
||||
Desc lipgloss.Style
|
||||
Command lipgloss.Style
|
||||
Updated lipgloss.Style
|
||||
}
|
||||
}
|
||||
|
||||
Repo struct {
|
||||
Base lipgloss.Style
|
||||
Title lipgloss.Style
|
||||
Command lipgloss.Style
|
||||
Body lipgloss.Style
|
||||
Header lipgloss.Style
|
||||
HeaderName lipgloss.Style
|
||||
HeaderDesc lipgloss.Style
|
||||
}
|
||||
|
||||
Footer lipgloss.Style
|
||||
Branch lipgloss.Style
|
||||
HelpKey lipgloss.Style
|
||||
HelpValue lipgloss.Style
|
||||
HelpDivider lipgloss.Style
|
||||
URLStyle lipgloss.Style
|
||||
|
||||
Error lipgloss.Style
|
||||
ErrorTitle lipgloss.Style
|
||||
ErrorBody lipgloss.Style
|
||||
|
||||
AboutNoReadme lipgloss.Style
|
||||
|
||||
LogItem struct {
|
||||
Normal struct {
|
||||
Base lipgloss.Style
|
||||
Hash lipgloss.Style
|
||||
Title lipgloss.Style
|
||||
Desc lipgloss.Style
|
||||
Keyword lipgloss.Style
|
||||
}
|
||||
Active struct {
|
||||
Base lipgloss.Style
|
||||
Hash lipgloss.Style
|
||||
Title lipgloss.Style
|
||||
Desc lipgloss.Style
|
||||
Keyword lipgloss.Style
|
||||
}
|
||||
}
|
||||
|
||||
Log struct {
|
||||
Commit lipgloss.Style
|
||||
CommitHash lipgloss.Style
|
||||
CommitAuthor lipgloss.Style
|
||||
CommitDate lipgloss.Style
|
||||
CommitBody lipgloss.Style
|
||||
CommitStatsAdd lipgloss.Style
|
||||
CommitStatsDel lipgloss.Style
|
||||
Paginator lipgloss.Style
|
||||
}
|
||||
|
||||
Ref struct {
|
||||
Normal struct {
|
||||
Item lipgloss.Style
|
||||
ItemTag lipgloss.Style
|
||||
}
|
||||
Active struct {
|
||||
Item lipgloss.Style
|
||||
ItemTag lipgloss.Style
|
||||
}
|
||||
ItemSelector lipgloss.Style
|
||||
ItemBranch lipgloss.Style
|
||||
Paginator lipgloss.Style
|
||||
}
|
||||
|
||||
Tree struct {
|
||||
Normal struct {
|
||||
FileName lipgloss.Style
|
||||
FileDir lipgloss.Style
|
||||
FileMode lipgloss.Style
|
||||
FileSize lipgloss.Style
|
||||
}
|
||||
Active struct {
|
||||
FileName lipgloss.Style
|
||||
FileDir lipgloss.Style
|
||||
FileMode lipgloss.Style
|
||||
FileSize lipgloss.Style
|
||||
}
|
||||
Selector lipgloss.Style
|
||||
FileContent lipgloss.Style
|
||||
Paginator lipgloss.Style
|
||||
NoItems lipgloss.Style
|
||||
}
|
||||
|
||||
Spinner lipgloss.Style
|
||||
|
||||
CodeNoContent lipgloss.Style
|
||||
|
||||
StatusBar lipgloss.Style
|
||||
StatusBarKey lipgloss.Style
|
||||
StatusBarValue lipgloss.Style
|
||||
StatusBarInfo lipgloss.Style
|
||||
StatusBarBranch lipgloss.Style
|
||||
StatusBarHelp lipgloss.Style
|
||||
|
||||
Tabs lipgloss.Style
|
||||
TabInactive lipgloss.Style
|
||||
TabActive lipgloss.Style
|
||||
TabSeparator lipgloss.Style
|
||||
}
|
||||
|
||||
// DefaultStyles returns default styles for the UI.
|
||||
func DefaultStyles() *Styles {
|
||||
highlightColor := lipgloss.Color("210")
|
||||
highlightColorDim := lipgloss.Color("174")
|
||||
selectorColor := lipgloss.Color("167")
|
||||
hashColor := lipgloss.Color("185")
|
||||
|
||||
s := new(Styles)
|
||||
|
||||
s.ActiveBorderColor = lipgloss.Color("62")
|
||||
s.InactiveBorderColor = lipgloss.Color("241")
|
||||
|
||||
s.App = lipgloss.NewStyle().
|
||||
Margin(1, 2)
|
||||
|
||||
s.ServerName = lipgloss.NewStyle().
|
||||
Height(1).
|
||||
MarginLeft(1).
|
||||
MarginBottom(1).
|
||||
Padding(0, 1).
|
||||
Background(lipgloss.Color("57")).
|
||||
Foreground(lipgloss.Color("229")).
|
||||
Bold(true)
|
||||
|
||||
s.TopLevelNormalTab = lipgloss.NewStyle().
|
||||
MarginRight(2)
|
||||
|
||||
s.TopLevelActiveTab = s.TopLevelNormalTab.Copy().
|
||||
Foreground(lipgloss.Color("36"))
|
||||
|
||||
s.TopLevelActiveTabDot = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("36"))
|
||||
|
||||
s.RepoSelector.Normal.Base = lipgloss.NewStyle().
|
||||
PaddingLeft(1).
|
||||
Border(lipgloss.Border{Left: " "}, false, false, false, true).
|
||||
Height(3)
|
||||
|
||||
s.RepoSelector.Normal.Title = lipgloss.NewStyle().Bold(true)
|
||||
|
||||
s.RepoSelector.Normal.Desc = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("243"))
|
||||
|
||||
s.RepoSelector.Normal.Command = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("132"))
|
||||
|
||||
s.RepoSelector.Normal.Updated = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("243"))
|
||||
|
||||
s.RepoSelector.Active.Base = s.RepoSelector.Normal.Base.Copy().
|
||||
BorderStyle(lipgloss.Border{Left: "┃"}).
|
||||
BorderForeground(lipgloss.Color("176"))
|
||||
|
||||
s.RepoSelector.Active.Title = s.RepoSelector.Normal.Title.Copy().
|
||||
Foreground(lipgloss.Color("212"))
|
||||
|
||||
s.RepoSelector.Active.Desc = s.RepoSelector.Normal.Desc.Copy().
|
||||
Foreground(lipgloss.Color("246"))
|
||||
|
||||
s.RepoSelector.Active.Updated = s.RepoSelector.Normal.Updated.Copy().
|
||||
Foreground(lipgloss.Color("212"))
|
||||
|
||||
s.RepoSelector.Active.Command = s.RepoSelector.Normal.Command.Copy().
|
||||
Foreground(lipgloss.Color("204"))
|
||||
|
||||
s.MenuItem = lipgloss.NewStyle().
|
||||
PaddingLeft(1).
|
||||
Border(lipgloss.Border{
|
||||
Left: " ",
|
||||
}, false, false, false, true).
|
||||
Height(3)
|
||||
|
||||
s.MenuLastUpdate = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("241")).
|
||||
Align(lipgloss.Right)
|
||||
|
||||
s.Repo.Base = lipgloss.NewStyle()
|
||||
|
||||
s.Repo.Title = lipgloss.NewStyle().
|
||||
Padding(0, 2)
|
||||
|
||||
s.Repo.Command = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("168"))
|
||||
|
||||
s.Repo.Body = lipgloss.NewStyle().
|
||||
Margin(1, 0)
|
||||
|
||||
s.Repo.Header = lipgloss.NewStyle().
|
||||
Height(2).
|
||||
Border(lipgloss.NormalBorder(), false, false, true, false).
|
||||
BorderForeground(lipgloss.Color("236"))
|
||||
|
||||
s.Repo.HeaderName = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("212")).
|
||||
Bold(true)
|
||||
|
||||
s.Repo.HeaderDesc = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("243"))
|
||||
|
||||
s.Footer = lipgloss.NewStyle().
|
||||
MarginTop(1).
|
||||
Padding(0, 1).
|
||||
Height(1)
|
||||
|
||||
s.Branch = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("203")).
|
||||
Background(lipgloss.Color("236")).
|
||||
Padding(0, 1)
|
||||
|
||||
s.HelpKey = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("241"))
|
||||
|
||||
s.HelpValue = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("239"))
|
||||
|
||||
s.HelpDivider = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("237")).
|
||||
SetString(" • ")
|
||||
|
||||
s.URLStyle = lipgloss.NewStyle().
|
||||
MarginLeft(1).
|
||||
Foreground(lipgloss.Color("168"))
|
||||
|
||||
s.Error = lipgloss.NewStyle().
|
||||
MarginTop(2)
|
||||
|
||||
s.ErrorTitle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("230")).
|
||||
Background(lipgloss.Color("204")).
|
||||
Bold(true).
|
||||
Padding(0, 1)
|
||||
|
||||
s.ErrorBody = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("252")).
|
||||
MarginLeft(2)
|
||||
|
||||
s.AboutNoReadme = lipgloss.NewStyle().
|
||||
MarginTop(1).
|
||||
MarginLeft(2).
|
||||
Foreground(lipgloss.Color("242"))
|
||||
|
||||
s.LogItem.Normal.Base = lipgloss.NewStyle().
|
||||
Border(lipgloss.Border{
|
||||
Left: " ",
|
||||
}, false, false, false, true).
|
||||
PaddingLeft(1)
|
||||
|
||||
s.LogItem.Active.Base = s.LogItem.Normal.Base.Copy().
|
||||
Border(lipgloss.Border{
|
||||
Left: "┃",
|
||||
}, false, false, false, true).
|
||||
BorderForeground(selectorColor)
|
||||
|
||||
s.LogItem.Active.Hash = s.LogItem.Normal.Hash.Copy().
|
||||
Foreground(hashColor)
|
||||
|
||||
s.LogItem.Active.Hash = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(highlightColor)
|
||||
|
||||
s.LogItem.Normal.Title = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("105"))
|
||||
|
||||
s.LogItem.Active.Title = lipgloss.NewStyle().
|
||||
Foreground(highlightColor).
|
||||
Bold(true)
|
||||
|
||||
s.LogItem.Normal.Desc = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("246"))
|
||||
|
||||
s.LogItem.Active.Desc = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("95"))
|
||||
|
||||
s.LogItem.Active.Keyword = s.LogItem.Active.Desc.Copy().
|
||||
Foreground(highlightColorDim)
|
||||
|
||||
s.LogItem.Normal.Hash = lipgloss.NewStyle().
|
||||
Foreground(hashColor)
|
||||
|
||||
s.LogItem.Active.Hash = lipgloss.NewStyle().
|
||||
Foreground(highlightColor)
|
||||
|
||||
s.Log.Commit = lipgloss.NewStyle().
|
||||
Margin(0, 2)
|
||||
|
||||
s.Log.CommitHash = lipgloss.NewStyle().
|
||||
Foreground(hashColor).
|
||||
Bold(true)
|
||||
|
||||
s.Log.CommitBody = lipgloss.NewStyle().
|
||||
MarginTop(1).
|
||||
MarginLeft(2)
|
||||
|
||||
s.Log.CommitStatsAdd = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("42")).
|
||||
Bold(true)
|
||||
|
||||
s.Log.CommitStatsDel = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("203")).
|
||||
Bold(true)
|
||||
|
||||
s.Log.Paginator = lipgloss.NewStyle().
|
||||
Margin(0).
|
||||
Align(lipgloss.Center)
|
||||
|
||||
s.Ref.Normal.Item = lipgloss.NewStyle()
|
||||
|
||||
s.Ref.ItemSelector = lipgloss.NewStyle().
|
||||
Foreground(selectorColor).
|
||||
SetString("> ")
|
||||
|
||||
s.Ref.Active.Item = lipgloss.NewStyle().
|
||||
Foreground(highlightColorDim)
|
||||
|
||||
s.Ref.ItemBranch = lipgloss.NewStyle()
|
||||
|
||||
s.Ref.Normal.ItemTag = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("39"))
|
||||
|
||||
s.Ref.Active.ItemTag = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(highlightColor)
|
||||
|
||||
s.Ref.Active.Item = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(highlightColor)
|
||||
|
||||
s.Ref.Paginator = s.Log.Paginator.Copy()
|
||||
|
||||
s.Tree.Selector = s.Tree.Normal.FileName.Copy().
|
||||
Width(1).
|
||||
Foreground(selectorColor)
|
||||
|
||||
s.Tree.Normal.FileName = lipgloss.NewStyle().
|
||||
MarginLeft(1)
|
||||
|
||||
s.Tree.Active.FileName = s.Tree.Normal.FileName.Copy().
|
||||
Bold(true).
|
||||
Foreground(highlightColor)
|
||||
|
||||
s.Tree.Normal.FileDir = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("39"))
|
||||
|
||||
s.Tree.Active.FileDir = lipgloss.NewStyle().
|
||||
Foreground(highlightColor)
|
||||
|
||||
s.Tree.Normal.FileMode = s.Tree.Active.FileName.Copy().
|
||||
Width(10).
|
||||
Foreground(lipgloss.Color("243"))
|
||||
|
||||
s.Tree.Active.FileMode = s.Tree.Normal.FileMode.Copy().
|
||||
Foreground(highlightColorDim)
|
||||
|
||||
s.Tree.Normal.FileSize = s.Tree.Normal.FileName.Copy().
|
||||
Foreground(lipgloss.Color("243"))
|
||||
|
||||
s.Tree.Active.FileSize = s.Tree.Normal.FileName.Copy().
|
||||
Foreground(highlightColorDim)
|
||||
|
||||
s.Tree.FileContent = lipgloss.NewStyle()
|
||||
|
||||
s.Tree.Paginator = s.Log.Paginator.Copy()
|
||||
|
||||
s.Tree.NoItems = s.AboutNoReadme.Copy()
|
||||
|
||||
s.Spinner = lipgloss.NewStyle().
|
||||
MarginTop(1).
|
||||
MarginLeft(2).
|
||||
Foreground(lipgloss.Color("205"))
|
||||
|
||||
s.CodeNoContent = lipgloss.NewStyle().
|
||||
SetString("No Content.").
|
||||
MarginTop(1).
|
||||
MarginLeft(2).
|
||||
Foreground(lipgloss.Color("242"))
|
||||
|
||||
s.StatusBar = lipgloss.NewStyle().
|
||||
Height(1)
|
||||
|
||||
s.StatusBarKey = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Padding(0, 1).
|
||||
Background(lipgloss.Color("206")).
|
||||
Foreground(lipgloss.Color("228"))
|
||||
|
||||
s.StatusBarValue = lipgloss.NewStyle().
|
||||
Padding(0, 1).
|
||||
Background(lipgloss.Color("235")).
|
||||
Foreground(lipgloss.Color("243"))
|
||||
|
||||
s.StatusBarInfo = lipgloss.NewStyle().
|
||||
Padding(0, 1).
|
||||
Background(lipgloss.Color("212")).
|
||||
Foreground(lipgloss.Color("230"))
|
||||
|
||||
s.StatusBarBranch = lipgloss.NewStyle().
|
||||
Padding(0, 1).
|
||||
Background(lipgloss.Color("62")).
|
||||
Foreground(lipgloss.Color("230"))
|
||||
|
||||
s.StatusBarHelp = lipgloss.NewStyle().
|
||||
Padding(0, 1).
|
||||
Background(lipgloss.Color("237")).
|
||||
Foreground(lipgloss.Color("243"))
|
||||
|
||||
s.Tabs = lipgloss.NewStyle().
|
||||
Height(1)
|
||||
|
||||
s.TabInactive = lipgloss.NewStyle()
|
||||
|
||||
s.TabActive = lipgloss.NewStyle().
|
||||
Underline(true).
|
||||
Foreground(lipgloss.Color("36"))
|
||||
|
||||
s.TabSeparator = lipgloss.NewStyle().
|
||||
SetString("│").
|
||||
Padding(0, 1).
|
||||
Foreground(lipgloss.Color("238"))
|
||||
|
||||
return s
|
||||
}
|
198
pkg/tui/tui.go
Normal file
198
pkg/tui/tui.go
Normal file
|
@ -0,0 +1,198 @@
|
|||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
zone "github.com/lrstanley/bubblezone"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/selector"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/keymap"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/contact_enterprise"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/source_configure"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/source_select"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/view_oss"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/pages/wizard_intro"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/styles"
|
||||
)
|
||||
|
||||
type page int
|
||||
|
||||
const (
|
||||
wizardIntroPage page = iota
|
||||
sourceSelectPage
|
||||
sourceConfigurePage
|
||||
viewOSSProjectPage
|
||||
contactEnterprisePage
|
||||
)
|
||||
|
||||
type sessionState int
|
||||
|
||||
const (
|
||||
startState sessionState = iota
|
||||
errorState
|
||||
loadedState
|
||||
)
|
||||
|
||||
// TUI is the main TUI model.
|
||||
type TUI struct {
|
||||
common common.Common
|
||||
pages []common.Component
|
||||
activePage page
|
||||
state sessionState
|
||||
args []string
|
||||
}
|
||||
|
||||
// New returns a new TUI model.
|
||||
func New(c common.Common) *TUI {
|
||||
ui := &TUI{
|
||||
common: c,
|
||||
pages: make([]common.Component, 5),
|
||||
activePage: wizardIntroPage,
|
||||
state: startState,
|
||||
}
|
||||
return ui
|
||||
}
|
||||
|
||||
// SetSize implements common.Component.
|
||||
func (ui *TUI) SetSize(width, height int) {
|
||||
ui.common.SetSize(width, height)
|
||||
for _, p := range ui.pages {
|
||||
if p != nil {
|
||||
p.SetSize(width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Init implements tea.Model.
|
||||
func (ui *TUI) Init() tea.Cmd {
|
||||
ui.pages[wizardIntroPage] = wizard_intro.New(ui.common)
|
||||
ui.pages[sourceSelectPage] = source_select.New(ui.common)
|
||||
ui.pages[sourceConfigurePage] = source_configure.New(ui.common)
|
||||
ui.pages[viewOSSProjectPage] = view_oss.New(ui.common)
|
||||
ui.pages[contactEnterprisePage] = contact_enterprise.New(ui.common)
|
||||
ui.SetSize(ui.common.Width, ui.common.Height)
|
||||
cmds := make([]tea.Cmd, 0)
|
||||
cmds = append(cmds,
|
||||
ui.pages[wizardIntroPage].Init(),
|
||||
ui.pages[sourceSelectPage].Init(),
|
||||
ui.pages[sourceConfigurePage].Init(),
|
||||
ui.pages[viewOSSProjectPage].Init(),
|
||||
ui.pages[contactEnterprisePage].Init(),
|
||||
)
|
||||
ui.state = loadedState
|
||||
ui.SetSize(ui.common.Width, ui.common.Height)
|
||||
return tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// Update implements tea.Model.
|
||||
func (ui *TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
cmds := make([]tea.Cmd, 0)
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
ui.SetSize(msg.Width, msg.Height)
|
||||
for i, p := range ui.pages {
|
||||
m, cmd := p.Update(msg)
|
||||
ui.pages[i] = m.(common.Component)
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
}
|
||||
case tea.KeyMsg, tea.MouseMsg:
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, ui.common.KeyMap.Help):
|
||||
case key.Matches(msg, ui.common.KeyMap.CmdQuit) && ui.activePage != sourceConfigurePage:
|
||||
return ui, tea.Quit
|
||||
case key.Matches(msg, ui.common.KeyMap.Quit):
|
||||
return ui, tea.Quit
|
||||
case ui.activePage > 0 && key.Matches(msg, ui.common.KeyMap.Back):
|
||||
ui.activePage -= 1
|
||||
return ui, nil
|
||||
}
|
||||
case tea.MouseMsg:
|
||||
switch msg.Type {
|
||||
case tea.MouseLeft:
|
||||
}
|
||||
}
|
||||
case common.ErrorMsg:
|
||||
return ui, nil
|
||||
case selector.SelectMsg:
|
||||
switch item := msg.IdentifiableItem.(type) {
|
||||
case wizard_intro.Item:
|
||||
switch item {
|
||||
case wizard_intro.Quit:
|
||||
cmds = append(cmds, tea.Quit)
|
||||
case wizard_intro.ViewOSSProject:
|
||||
ui.activePage = viewOSSProjectPage
|
||||
case wizard_intro.ViewHelpDocs:
|
||||
ui.args = []string{"--help"}
|
||||
|
||||
return ui, tea.Batch(nil, tea.Quit)
|
||||
case wizard_intro.EnterpriseInquire:
|
||||
ui.activePage = contactEnterprisePage
|
||||
case wizard_intro.ScanSourceWithWizard:
|
||||
ui.activePage = sourceSelectPage
|
||||
}
|
||||
case source_select.SourceItem:
|
||||
ui.activePage = sourceConfigurePage
|
||||
cmds = append(cmds, func() tea.Msg {
|
||||
return source_configure.SetSourceMsg{Source: item.ID()}
|
||||
})
|
||||
}
|
||||
case source_configure.SetArgsMsg:
|
||||
ui.args = strings.Split(string(msg), " ")[1:]
|
||||
return ui, tea.Quit
|
||||
}
|
||||
|
||||
if ui.state == loadedState {
|
||||
m, cmd := ui.pages[ui.activePage].Update(msg)
|
||||
ui.pages[ui.activePage] = m.(common.Component)
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// This fixes determining the height margin of the footer.
|
||||
// ui.SetSize(ui.common.Width, ui.common.Height)
|
||||
return ui, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// View implements tea.Model.
|
||||
func (ui *TUI) View() string {
|
||||
var view string
|
||||
switch ui.state {
|
||||
case startState:
|
||||
view = "Loading..."
|
||||
case loadedState:
|
||||
view = ui.pages[ui.activePage].View()
|
||||
default:
|
||||
view = "Unknown state :/ this is a bug!"
|
||||
}
|
||||
return ui.common.Zone.Scan(
|
||||
ui.common.Styles.App.Render(view),
|
||||
)
|
||||
}
|
||||
|
||||
func Run() []string {
|
||||
c := common.Common{
|
||||
Copy: nil,
|
||||
Styles: styles.DefaultStyles(),
|
||||
KeyMap: keymap.DefaultKeyMap(),
|
||||
Width: 0,
|
||||
Height: 0,
|
||||
Zone: zone.New(),
|
||||
}
|
||||
m := New(c)
|
||||
p := tea.NewProgram(m)
|
||||
// TODO: Print normal help message.
|
||||
if _, err := p.Run(); err != nil {
|
||||
fmt.Printf("Alas, there's been an error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return m.args
|
||||
}
|
Loading…
Reference in a new issue