fix(tui): center all tabs, full-screen SFTP, muted inline keybindings

This commit is contained in:
swanadiva
2026-06-23 17:12:12 +07:00
parent c7568e0bf4
commit 7f197b9e3a
8 changed files with 355 additions and 232 deletions
+72 -27
View File
@@ -7,6 +7,7 @@ import (
"sync"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"git.tukangketik.id/swanadiva/hostkeeper/internal/models"
"git.tukangketik.id/swanadiva/hostkeeper/pkg/storage"
@@ -134,52 +135,96 @@ func (t *KeyListTab) View() string {
t.mu.Lock()
defer t.mu.Unlock()
var b strings.Builder
b.WriteString(AppTitleStyle.Render("SSH Key Pairs"))
b.WriteString("\n\n")
switch t.state {
case keyListLoading:
b.WriteString(SubtitleStyle.Render("Loading keys..."))
return b.String()
case keyListError:
b.WriteString(ErrorStyle.Render(fmt.Sprintf("Error: %v", t.err)))
b.WriteString("\n\n")
b.WriteString(SubtitleStyle.Render("Press Esc to go back"))
if t.state == keyListLoading {
return lipgloss.PlaceHorizontal(t.width, lipgloss.Center,
SubtitleStyle.Render("Loading keys..."))
}
if t.state == keyListError {
var b strings.Builder
b.WriteString(lipgloss.PlaceHorizontal(t.width, lipgloss.Center,
ErrorStyle.Render(fmt.Sprintf("Error: %v", t.err))))
b.WriteString("\n")
b.WriteString(lipgloss.PlaceHorizontal(t.width, lipgloss.Center,
SubtitleStyle.Render("Press Esc to go back")))
return b.String()
}
type styledRow struct {
text string
plain string
}
var rows []styledRow
title := "SSH Key Pairs"
titlePlain := title
if t.err != nil {
b.WriteString(ErrorStyle.Render(fmt.Sprintf("Error: %v", t.err)))
b.WriteString("\n\n")
errLine := fmt.Sprintf("Error: %v", t.err)
rows = append(rows, styledRow{text: ErrorStyle.Render(errLine), plain: errLine})
}
if len(t.keys) == 0 {
b.WriteString(SubtitleStyle.Render("No SSH keys stored."))
b.WriteString("\n")
b.WriteString(SubtitleStyle.Render("Press Ctrl+N to add a new key."))
empty := "No SSH keys stored."
hint := "Press Ctrl+N to add a new key."
rows = append(rows, styledRow{text: SubtitleStyle.Render(empty), plain: empty})
rows = append(rows, styledRow{text: SubtitleStyle.Render(hint), plain: hint})
} else {
for i, key := range t.keys {
var line string
var plain string
if key.Type != "" {
line = fmt.Sprintf(" %s (%s)", key.Name, key.Type)
plain = fmt.Sprintf(" %s (%s)", key.Name, key.Type)
} else {
line = fmt.Sprintf(" %s", key.Name)
plain = fmt.Sprintf(" %s", key.Name)
}
var styled string
if i == t.selected {
b.WriteString(SelectedStyle.Render(line))
styled = lipgloss.NewStyle().
Foreground(gbFg).
Background(gbBgSel).
Bold(true).
Render("▸ " + strings.TrimLeft(plain, " "))
} else {
b.WriteString(HostNameStyle.Render(line))
styled = lipgloss.NewStyle().Foreground(gbFg).Render(plain)
}
b.WriteString("\n")
rows = append(rows, styledRow{text: styled, plain: plain})
}
}
b.WriteString("\n")
b.WriteString(SubtitleStyle.Render("Ctrl+N:add Ctrl+E:edit D:delete Esc:back"))
footerPlain := "Ctrl+N:add Ctrl+E:edit D:delete Esc:back Ctrl+Tab:switch Ctrl+Q:close"
footer := styledRow{text: SubtitleStyle.Render(footerPlain), plain: footerPlain}
widestRow := lipgloss.Width(titlePlain)
for _, r := range rows {
if w := lipgloss.Width(r.plain); w > widestRow {
widestRow = w
}
}
if w := lipgloss.Width(footer.plain); w > widestRow {
widestRow = w
}
const sidePad = 6
targetW := widestRow + sidePad*2
rowShift := (targetW - widestRow) / 2
var inner strings.Builder
titleStyled := lipgloss.NewStyle().Bold(true).Foreground(gbYellow).Render(titlePlain)
inner.WriteString(lipgloss.PlaceHorizontal(targetW, lipgloss.Center, titleStyled))
inner.WriteString("\n\n")
shift := strings.Repeat(" ", rowShift)
for _, r := range rows {
line := lipgloss.PlaceHorizontal(targetW, lipgloss.Left, shift+r.text)
inner.WriteString(line)
inner.WriteString("\n")
}
inner.WriteString("\n")
inner.WriteString(lipgloss.PlaceHorizontal(targetW, lipgloss.Center, footer.text))
box := BorderStyle.Render(inner.String())
var b strings.Builder
b.WriteString(lipgloss.PlaceHorizontal(t.width, lipgloss.Center, box))
return b.String()
}