diff --git a/internal/ui/app/modal.go b/internal/ui/app/modal.go index 8b27d91..0dac172 100644 --- a/internal/ui/app/modal.go +++ b/internal/ui/app/modal.go @@ -40,6 +40,7 @@ type option struct { group string // the source row it's in (search) kind provider.Kind // the kind of provider it's from (search) hits []int // characters of the label the filter matched (Go to) + plain bool // a key in a plain env file: no key to encrypt with (Go to) run func(m *Model) tea.Cmd // header makes this a section title: not selectable, and hidden // while a filter narrows the list. diff --git a/internal/ui/app/search.go b/internal/ui/app/search.go index f151c17..0b4cc47 100644 --- a/internal/ui/app/search.go +++ b/internal/ui/app/search.go @@ -61,13 +61,14 @@ func (m *Model) searchEntries() (sources, items []option) { var es []entry // key is the store key, so ctrl+f stars the entry; mark shows how // much is loaded, reload (ctrl+r) loads it again. - add := func(depth int, row string, kind provider.Kind, path []string, account, key, mark string, run, reload func(*Model) tea.Cmd) { + add := func(depth int, row string, kind provider.Kind, path []string, account, key, mark string, run, reload func(*Model) tea.Cmd) *option { n := m.store.Node(key) sort := strings.ToLower(strings.Join(path, "/")) + " " + account es = append(es, entry{depth, sort, option{label: m.pathLabel(kind, path, account), note: n.Note, star: key, starAccount: providerOf(row), starred: n.StarredFor(providerOf(row)), mark: mark, run: run, reload: reload, id: sort, group: row, kind: kind}}) + return &es[len(es)-1].opt } for _, p := range m.providers { for _, src := range m.sourceResults[p.ID()].sources { @@ -98,10 +99,11 @@ func (m *Model) searchEntries() (sources, items []option) { typ := strings.TrimSuffix(strings.ToLower(pluralLabels[t]), "s") // "key" suffix = strings.Trim(typ+" · "+suffix, " ·") } - add(2, row, c.Source.Kind, []string{src.Name, c.Name, o.Name}, suffix, objectKey(o), " ", func(m *Model) tea.Cmd { + e := add(2, row, c.Source.Kind, []string{src.Name, c.Name, o.Name}, suffix, objectKey(o), " ", func(m *Model) tea.Cmd { m.startNav(r, actGo, row) return nil }, reload) + e.plain = c.Detail == provider.DetailPlainEnv } } } diff --git a/internal/ui/app/searchview.go b/internal/ui/app/searchview.go index 90b790f..790542e 100644 --- a/internal/ui/app/searchview.go +++ b/internal/ui/app/searchview.go @@ -56,8 +56,9 @@ type searchView struct { // pick, if set, makes Go to a picker of secrets, for title: it lists // secrets only, and enter gives the selected one's link to pick // instead of going there. - pick func(m *Model, r ref.Ref) tea.Cmd - pickTitle, pickNote string + pick func(m *Model, r ref.Ref) tea.Cmd + pickTitle string + pickLines []string // what picking does, above the input // loaded are the scopes a search has loaded since the popup opened: // typing loads the scope once, so lists that fail aren't asked for @@ -749,7 +750,14 @@ func (m Model) searchViewBox() string { lists = panes.BoxCounted(m.st, title(resultsTitle, true), "", counter(v.res, len(v.shown)), right, resW, listH, focused) } - body := []string{" " + v.input.View(), ""} + var body []string + if v.pick != nil { + for _, l := range v.pickLines { + body = append(body, " "+l) + } + body = append(body, "") + } + body = append(body, " "+v.input.View(), "") for _, l := range strings.Split(lists, "\n") { body = append(body, " "+l) } @@ -761,9 +769,6 @@ func (m Model) searchViewBox() string { } else { body = append(body, "") // the same height on every tab } - if v.pick != nil && v.pickNote != "" { - body = append(body, " "+m.st.Muted.Render(v.pickNote)) - } km := m.keys var foot string switch { @@ -808,10 +813,10 @@ func (v *searchView) selectLabel() string { } // pickable is the link of a Go to entry that a picker can pick: a -// secret (not a key or certificate). +// secret (not a key or certificate, nor a key in a plain env file). func pickable(o option) (ref.Ref, bool) { r, err := ref.Parse(o.star) - if err != nil || r.IsShare() || r.IsSource() || r.IsContainer() || (r.Type != "" && r.Type != provider.TypeSecret) { + if err != nil || o.plain || r.IsShare() || r.IsSource() || r.IsContainer() || (r.Type != "" && r.Type != provider.TypeSecret) { return ref.Ref{}, false } return r, true diff --git a/internal/ui/app/sharefiles.go b/internal/ui/app/sharefiles.go index 6c14a8d..a25388c 100644 --- a/internal/ui/app/sharefiles.go +++ b/internal/ui/app/sharefiles.go @@ -131,7 +131,15 @@ func (m *Model) pickShareKey(s sharing) tea.Cmd { return nil } m.search.pickTitle = "Encrypt with which secret?" - m.search.pickNote = "Whoever can read it can open the link. share.key sets a default." + what := s.files[0].Path + if len(s.files) > 1 { + what = plural(len(s.files), "env file") + } + m.search.pickLines = []string{ + "Sharing " + what + " from " + s.repo.Name + " as an encrypted link.", + "Pick the secret to encrypt with: whoever can read it can open the link.", + m.st.Muted.Render("Its value isn't shown or put in the link. share.key sets a default."), + } m.fillSearch(false) return tea.Batch(cmd, m.searchLoads()) } diff --git a/internal/ui/app/sharefiles_test.go b/internal/ui/app/sharefiles_test.go index dc0ea08..5f99097 100644 --- a/internal/ui/app/sharefiles_test.go +++ b/internal/ui/app/sharefiles_test.go @@ -47,6 +47,14 @@ func shareFrom(t *testing.T, root string, paste bool) string { if v == nil || v.pick == nil { t.Fatalf("no secret picker: %s", d.m.flash.text) } + for _, o := range v.shown { + if o.plain || strings.Contains(o.label, ".env") { + t.Errorf("a plain env value offered as the key: %s", o.label) + } + } + if !strings.Contains(d.screen(), "as an encrypted link") { + t.Errorf("doesn't say what picking does:\n%s", d.screen()) + } if paste { // A reference pasted is picked as it is. d.send(tea.PasteMsg{Content: "https://" + kv + "/secrets/db-password"})