feat(vault-tui): implement KV client and service for managing secrets
- Added internal/vault/client.go for creating a Vault client with configuration settings. - Introduced internal/vault/errors.go to classify Vault API errors for better UI handling. - Created internal/vault/kv.go to manage KV secrets, including listing, reading, writing, and deleting operations. - Implemented internal/vault/mounts.go to list and describe secret engine mounts. - Developed internal/vault/service.go to provide a unified entry point for Vault operations. - Added internal/vault/kv_test.go for comprehensive testing of KV operations. - Introduced internal/ui/toast.go for transient notifications in the UI. - Added renovate.json for dependency management and updates.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"charm.land/bubbles/v2/key"
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
|
||||
"git.morlana.online/f.weber/vault-tui/internal/ui/keys"
|
||||
)
|
||||
|
||||
// textInputActive reports whether a free-text field currently owns the
|
||||
// keyboard, so global single-letter bindings (help "?", profiles "p", ...)
|
||||
// don't get eaten while the user is typing a value/filter that happens to
|
||||
// contain that letter.
|
||||
func (m *Model) textInputActive() bool {
|
||||
if m.top() == scrEditor {
|
||||
return true
|
||||
}
|
||||
if m.top() == scrAuth && m.auth.phase == 1 {
|
||||
return true
|
||||
}
|
||||
if (m.top() == scrMounts || m.top() == scrBrowser) && m.listReady && m.list.SettingFilter() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Model) openProfilePicker() (tea.Model, tea.Cmd) {
|
||||
names := make([]string, 0, len(m.a.File.Profiles))
|
||||
for n := range m.a.File.Profiles {
|
||||
names = append(names, n)
|
||||
}
|
||||
sort.Strings(names)
|
||||
cursor := 0
|
||||
for i, n := range names {
|
||||
if n == m.a.Settings.Profile {
|
||||
cursor = i
|
||||
}
|
||||
}
|
||||
m.profile = profileState{open: true, names: names, cursor: cursor}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *Model) updateProfilePicker(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
|
||||
switch {
|
||||
case key.Matches(msg, m.keys.Up):
|
||||
if m.profile.cursor > 0 {
|
||||
m.profile.cursor--
|
||||
}
|
||||
case key.Matches(msg, m.keys.Down):
|
||||
if m.profile.cursor < len(m.profile.names)-1 {
|
||||
m.profile.cursor++
|
||||
}
|
||||
case key.Matches(msg, m.keys.Enter):
|
||||
return m.switchToSelectedProfile()
|
||||
case key.Matches(msg, m.keys.Back), key.Matches(msg, m.keys.Quit):
|
||||
m.profile.open = false
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *Model) switchToSelectedProfile() (tea.Model, tea.Cmd) {
|
||||
name := m.profile.names[m.profile.cursor]
|
||||
m.profile.open = false
|
||||
if name == m.a.Settings.Profile {
|
||||
return m, nil
|
||||
}
|
||||
if err := m.a.SwitchProfile(name); err != nil {
|
||||
return m, m.notify(toastErr, "switch profile: %v", err)
|
||||
}
|
||||
|
||||
m.svc = m.a.Service()
|
||||
km := keys.Default()
|
||||
_ = km.Apply(m.a.File.Keys) // already validated once at startup; config hasn't changed
|
||||
if m.a.Settings.ReadOnly {
|
||||
km.DisableWrites()
|
||||
}
|
||||
m.keys = km
|
||||
m.tokenInfo = nil
|
||||
m.listSelect = ""
|
||||
return m, m.startAuth()
|
||||
}
|
||||
|
||||
func (m *Model) confirmLogout() (tea.Model, tea.Cmd) {
|
||||
profile := m.a.Settings.Profile
|
||||
action := func() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return logoutDoneMsg{err: m.a.Store.Erase(m.ctx)}
|
||||
}
|
||||
}
|
||||
m.confirm = &confirmSpec{
|
||||
title: "Log out?",
|
||||
body: "Forgets the saved token for profile " + profile + ". This does not revoke it in Vault.",
|
||||
onConfirm: action,
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *Model) viewProfilePicker() string {
|
||||
lines := []string{m.styles.ModalTitle.Render("Switch profile"), ""}
|
||||
for i, n := range m.profile.names {
|
||||
marker := " "
|
||||
if n == m.a.Settings.Profile {
|
||||
marker = m.styles.SuccessText.Render(symCurrent) + " "
|
||||
}
|
||||
line := marker + n
|
||||
if i == m.profile.cursor {
|
||||
line = m.styles.RowSelected.Render(padRight(marker+n, 28))
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
lines = append(lines, "", m.styles.Help.Render("↑/↓ move • enter switch • esc cancel"))
|
||||
return m.styles.Modal.Render(lipgloss.JoinVertical(lipgloss.Left, lines...))
|
||||
}
|
||||
Reference in New Issue
Block a user