fix: keep tui status and help context truthful
This commit is contained in:
parent
84a61d4895
commit
37c4fd34a9
|
|
@ -146,6 +146,9 @@ func runTUI() error {
|
|||
v.Lock()
|
||||
return nil
|
||||
}
|
||||
tui.VaultUnlocked = func() bool {
|
||||
return getOrCreateVault().IsUnlocked()
|
||||
}
|
||||
tui.UpdateTestResult = func(alias string, status model.TestStatus, testErr string) error {
|
||||
return appDB.UpdateTestResult(alias, status, testErr)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ var (
|
|||
DeleteForward func(forwardID int64) error
|
||||
ImportServers func() (int, error)
|
||||
LockVault func() error
|
||||
VaultUnlocked func() bool
|
||||
)
|
||||
|
||||
// --- Screen type ---
|
||||
|
|
@ -270,6 +271,8 @@ type tuiModel struct {
|
|||
forwardForm *forwardFormModel
|
||||
confirm *confirmState
|
||||
fullHelp *fullHelpModel
|
||||
helpParent screen
|
||||
vaultUnlocked bool
|
||||
}
|
||||
|
||||
func New(servers []*model.Server) *tuiModel {
|
||||
|
|
@ -297,15 +300,21 @@ func New(servers []*model.Server) *tuiModel {
|
|||
templateList.SetShowHelp(false)
|
||||
tagList := newStringList(nil, "Tags", 0, 0)
|
||||
|
||||
vaultIsUnlocked := true
|
||||
if VaultUnlocked != nil {
|
||||
vaultIsUnlocked = VaultUnlocked()
|
||||
}
|
||||
|
||||
return &tuiModel{
|
||||
screen: screenList,
|
||||
list: l,
|
||||
servers: servers,
|
||||
searchInput: search,
|
||||
selected: map[string]bool{},
|
||||
tagInput: tagInput,
|
||||
templateList: templateList,
|
||||
tagList: tagList,
|
||||
screen: screenList,
|
||||
list: l,
|
||||
servers: servers,
|
||||
searchInput: search,
|
||||
selected: map[string]bool{},
|
||||
tagInput: tagInput,
|
||||
templateList: templateList,
|
||||
tagList: tagList,
|
||||
vaultUnlocked: vaultIsUnlocked,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -328,6 +337,37 @@ func (m *tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
m.form.width = msg.Width
|
||||
m.form.height = msg.Height
|
||||
}
|
||||
if m.templateForm != nil {
|
||||
m.templateForm.width = msg.Width
|
||||
m.templateForm.height = msg.Height
|
||||
}
|
||||
if m.forwardScreen != nil {
|
||||
m.forwardScreen.width = msg.Width
|
||||
m.forwardScreen.height = msg.Height
|
||||
}
|
||||
if m.forwardForm != nil {
|
||||
m.forwardForm.width = msg.Width
|
||||
m.forwardForm.height = msg.Height
|
||||
}
|
||||
if m.tunnelScreen != nil {
|
||||
m.tunnelScreen.width = msg.Width
|
||||
m.tunnelScreen.height = msg.Height
|
||||
m.tunnelScreen.list.SetSize(msg.Width, managerListHeight(msg.Height))
|
||||
}
|
||||
if m.helpScreen != nil {
|
||||
updated, _ := m.helpScreen.Update(msg)
|
||||
if help, ok := updated.(*helpScreenModel); ok {
|
||||
m.helpScreen = help
|
||||
}
|
||||
}
|
||||
if m.fullHelp != nil {
|
||||
m.fullHelp.width = msg.Width
|
||||
m.fullHelp.height = msg.Height
|
||||
}
|
||||
if m.actionMenu != nil {
|
||||
m.actionMenu.width = msg.Width
|
||||
m.actionMenu.list.SetSize(msg.Width, managerListHeight(msg.Height))
|
||||
}
|
||||
m.templateList.SetSize(msg.Width, managerListHeight(msg.Height))
|
||||
m.tagList.SetSize(msg.Width, managerListHeight(msg.Height))
|
||||
return m, nil
|
||||
|
|
@ -571,6 +611,22 @@ func (m *tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
return m, nil
|
||||
|
||||
case tea.KeyMsg:
|
||||
if m.err != nil || m.success != "" {
|
||||
m.err = nil
|
||||
m.success = ""
|
||||
}
|
||||
if msg.Type == tea.KeyF1 && m.screen != screenHelp && m.screen != screenFullHelp && m.screen != screenConfirm {
|
||||
m.helpParent = m.screen
|
||||
m.fullHelp = newFullHelpModel(m.width, m.height)
|
||||
m.screen = screenFullHelp
|
||||
return m, nil
|
||||
}
|
||||
if msg.Type == tea.KeyRunes && msg.String() == "?" && !m.screenOwnsPrintableInput() && m.screen != screenHelp && m.screen != screenFullHelp && m.screen != screenConfirm {
|
||||
m.helpParent = m.screen
|
||||
m.helpScreen = newHelpScreenModel(m.width, m.height)
|
||||
m.screen = screenHelp
|
||||
return m, nil
|
||||
}
|
||||
switch m.screen {
|
||||
case screenList:
|
||||
return m.updateList(msg)
|
||||
|
|
@ -687,16 +743,12 @@ func (m *tuiModel) updateList(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||
|
||||
case tea.KeyRunes:
|
||||
if msg.String() == "?" {
|
||||
m.helpParent = m.screen
|
||||
m.helpScreen = newHelpScreenModel(m.width, m.height)
|
||||
m.screen = screenHelp
|
||||
return m, nil
|
||||
}
|
||||
|
||||
case tea.KeyF1:
|
||||
m.fullHelp = newFullHelpModel(m.width, m.height)
|
||||
m.screen = screenFullHelp
|
||||
return m, nil
|
||||
|
||||
case tea.KeyCtrlW:
|
||||
// Open forward manager for selected server
|
||||
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
||||
|
|
@ -1118,11 +1170,9 @@ func (m *tuiModel) View() string {
|
|||
|
||||
if m.err != nil {
|
||||
b.WriteString("\n" + errorStyle.Render(fmt.Sprintf("Error: %v", m.err)))
|
||||
m.err = nil
|
||||
}
|
||||
if m.success != "" {
|
||||
b.WriteString("\n" + successStyle.Render(m.success))
|
||||
m.success = ""
|
||||
}
|
||||
|
||||
return b.String()
|
||||
|
|
@ -1134,8 +1184,8 @@ func (m *tuiModel) updateHelp(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||
m.helpScreen = hs
|
||||
}
|
||||
// Esc or Enter closes help
|
||||
if msg.Type == tea.KeyEsc || msg.Type == tea.KeyEnter {
|
||||
m.screen = screenList
|
||||
if msg.Type == tea.KeyEsc || msg.Type == tea.KeyEnter || (msg.Type == tea.KeyRunes && (msg.String() == "q" || msg.String() == "Q" || msg.String() == "?")) {
|
||||
m.screen = m.helpParent
|
||||
m.helpScreen = nil
|
||||
return m, nil
|
||||
}
|
||||
|
|
@ -1260,6 +1310,7 @@ func (m *tuiModel) updateActionMenu(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||
} else if err := LockVault(); err != nil {
|
||||
m.err = err
|
||||
} else {
|
||||
m.vaultUnlocked = false
|
||||
m.success = "Vault locked."
|
||||
}
|
||||
case "vault_change_pw":
|
||||
|
|
@ -1524,14 +1575,23 @@ func (m *tuiModel) updateFullHelp(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||
if fh, ok := updated.(*fullHelpModel); ok {
|
||||
m.fullHelp = fh
|
||||
}
|
||||
if msg.Type == tea.KeyEsc || msg.Type == tea.KeyEnter {
|
||||
m.screen = screenList
|
||||
if msg.Type == tea.KeyEsc || msg.Type == tea.KeyEnter || (msg.Type == tea.KeyRunes && (msg.String() == "q" || msg.String() == "Q")) {
|
||||
m.screen = m.helpParent
|
||||
m.fullHelp = nil
|
||||
return m, nil
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *tuiModel) screenOwnsPrintableInput() bool {
|
||||
switch m.screen {
|
||||
case screenForm, screenSearch, screenTagInput, screenTemplateForm, screenForwardForm:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (m *tuiModel) updateForwardForm(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
if msg.Type == tea.KeyEsc {
|
||||
m.screen = screenForwardList
|
||||
|
|
@ -1562,7 +1622,11 @@ func (m *tuiModel) viewServerList() string {
|
|||
|
||||
b.WriteString(titleStyle.Render(fmt.Sprintf("sshkeeper %d servers", len(m.servers))))
|
||||
b.WriteString("\n")
|
||||
b.WriteString(helpStyle.Render(fmt.Sprintf("Vault unlocked | %s", testSummary(m.servers))))
|
||||
vaultStatus := "Vault locked"
|
||||
if m.vaultUnlocked {
|
||||
vaultStatus = "Vault unlocked"
|
||||
}
|
||||
b.WriteString(helpStyle.Render(fmt.Sprintf("%s | %s", vaultStatus, testSummary(m.servers))))
|
||||
b.WriteString("\n\n")
|
||||
b.WriteString(listHeaderStyle.Render(fmt.Sprintf(" %-20s %-20s %-34s %-12s %-10s %s", "NAME", "ALIAS", "ROUTE", "AUTH", "GROUP", "STATUS")))
|
||||
b.WriteString("\n")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
package tui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func TestVaultStatusTracksSuccessfulLock(t *testing.T) {
|
||||
oldUnlocked, oldLock := VaultUnlocked, LockVault
|
||||
t.Cleanup(func() { VaultUnlocked, LockVault = oldUnlocked, oldLock })
|
||||
VaultUnlocked = func() bool { return true }
|
||||
LockVault = func() error { return nil }
|
||||
|
||||
m := New(nil)
|
||||
if !strings.Contains(m.View(), "Vault unlocked") {
|
||||
t.Fatalf("initial status is not unlocked:\n%s", m.View())
|
||||
}
|
||||
m.actionMenu = newActionMenuModel(80, 24)
|
||||
m.screen = screenActionMenu
|
||||
for i := range m.actionMenu.list.Items() {
|
||||
m.actionMenu.list.Select(i)
|
||||
item, ok := m.actionMenu.list.SelectedItem().(actionMenuItem)
|
||||
if ok && item.action == "vault_lock" {
|
||||
break
|
||||
}
|
||||
}
|
||||
updated, _ := m.updateActionMenu(tea.KeyMsg{Type: tea.KeyEnter})
|
||||
m = updated.(*tuiModel)
|
||||
view := m.View()
|
||||
if !strings.Contains(view, "Vault locked") || strings.Contains(view, "Vault unlocked") {
|
||||
t.Fatalf("status after lock is false:\n%s", view)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationSurvivesRepeatedView(t *testing.T) {
|
||||
m := New(nil)
|
||||
m.err = errors.New("reload failed")
|
||||
first := m.View()
|
||||
second := m.View()
|
||||
if !strings.Contains(first, "reload failed") || !strings.Contains(second, "reload failed") {
|
||||
t.Fatalf("notification was consumed by View: first=%q second=%q", first, second)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFullHelpReturnsToOriginatingScreen(t *testing.T) {
|
||||
m := New(nil)
|
||||
m.screen = screenForwardList
|
||||
m.forwardScreen = newForwardScreenModel(1, "prod", 80, 24)
|
||||
|
||||
updated, _ := m.Update(tea.KeyMsg{Type: tea.KeyF1})
|
||||
m = updated.(*tuiModel)
|
||||
if m.screen != screenFullHelp || m.fullHelp == nil {
|
||||
t.Fatalf("F1 did not open full help from forward list: screen=%v", m.screen)
|
||||
}
|
||||
updated, _ = m.Update(tea.KeyMsg{Type: tea.KeyEsc})
|
||||
m = updated.(*tuiModel)
|
||||
if m.screen != screenForwardList || m.fullHelp != nil {
|
||||
t.Fatalf("help did not return to forward list: screen=%v help=%v", m.screen, m.fullHelp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextHelpReturnsToOriginatingManager(t *testing.T) {
|
||||
m := New(nil)
|
||||
m.screen = screenForwardList
|
||||
m.forwardScreen = newForwardScreenModel(1, "prod", 80, 24)
|
||||
|
||||
updated, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'?'}})
|
||||
m = updated.(*tuiModel)
|
||||
if m.screen != screenHelp || m.helpScreen == nil {
|
||||
t.Fatalf("? did not open help from manager: screen=%v", m.screen)
|
||||
}
|
||||
updated, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'q'}})
|
||||
m = updated.(*tuiModel)
|
||||
if m.screen != screenForwardList || m.helpScreen != nil {
|
||||
t.Fatalf("q did not return to manager: screen=%v help=%v", m.screen, m.helpScreen)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelpShortcutDoesNotStealQuestionMarkFromSearch(t *testing.T) {
|
||||
m := New(nil)
|
||||
m.screen = screenSearch
|
||||
m.searchInput.Focus()
|
||||
updated, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'?'}})
|
||||
m = updated.(*tuiModel)
|
||||
if m.screen != screenSearch || m.searchInput.Value() != "?" {
|
||||
t.Fatalf("search input lost printable rune: screen=%v value=%q", m.screen, m.searchInput.Value())
|
||||
}
|
||||
}
|
||||
|
||||
func TestResizePropagatesToActiveChildren(t *testing.T) {
|
||||
m := New(nil)
|
||||
m.form = newFormModel(80, 24)
|
||||
m.forwardScreen = newForwardScreenModel(1, "prod", 80, 24)
|
||||
m.forwardForm = newForwardFormModel(1, 80, 24)
|
||||
m.templateForm = newTemplateFormModel(nil, 80, 24)
|
||||
m.tunnelScreen = newTunnelScreenModel(80, 24)
|
||||
m.helpScreen = newHelpScreenModel(80, 24)
|
||||
m.fullHelp = newFullHelpModel(80, 24)
|
||||
m.actionMenu = newActionMenuModel(80, 24)
|
||||
|
||||
updated, _ := m.Update(tea.WindowSizeMsg{Width: 60, Height: 16})
|
||||
m = updated.(*tuiModel)
|
||||
if m.form.width != 60 || m.form.height != 16 ||
|
||||
m.forwardScreen.width != 60 || m.forwardScreen.height != 16 ||
|
||||
m.forwardForm.width != 60 || m.forwardForm.height != 16 ||
|
||||
m.templateForm.width != 60 || m.templateForm.height != 16 ||
|
||||
m.tunnelScreen.width != 60 || m.tunnelScreen.height != 16 ||
|
||||
m.helpScreen.width != 60 || m.fullHelp.width != 60 || m.fullHelp.height != 16 {
|
||||
t.Fatalf("resize did not reach every child: %#v", m)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue