Files
vault-tui/internal/ui/screen_profile.go
T
f.weber ae30ba1240 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.
2026-08-14 11:09:03 +02:00

117 lines
3.0 KiB
Go

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