feat: implement basic TUI framework with host list
- Add Bubble Tea TUI model with Init/Update/View - Implement host list screen with keyboard navigation (up/down/enter/q) - Add styled rendering for hosts, selection, tags - Create hostkeeper tui CLI command - Add tests for TUI initialization and host loading - Update project state documentation
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
|
||||
"git.tukangketik.id/swanadiva/hostkeeper/internal/models"
|
||||
)
|
||||
|
||||
var (
|
||||
HostNameStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("226")).Bold(true)
|
||||
HostDetailStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
|
||||
SelectedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Background(lipgloss.Color("235")).Padding(0, 1)
|
||||
TagStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("86"))
|
||||
)
|
||||
|
||||
// renderHostList renders the host list screen
|
||||
func renderHostList(m *Model) string {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(TitleStyle.Render("HOSTKEEPER - SSH Manager"))
|
||||
b.WriteString("\n\n")
|
||||
|
||||
if len(m.Hosts) == 0 {
|
||||
b.WriteString(SubtitleStyle.Render("No hosts found. Add your first host with: hostkeeper add"))
|
||||
b.WriteString("\n\n")
|
||||
b.WriteString(InfoStyle.Render("Press 'q' to quit"))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
for i, host := range m.Hosts {
|
||||
if i == m.SelectedIndex {
|
||||
b.WriteString(renderSelectedHost(host))
|
||||
} else {
|
||||
b.WriteString(renderHost(host))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
b.WriteString("\n")
|
||||
b.WriteString(SubtitleStyle.Render("\u2191\u2193: Navigate | Enter: Connect | q: Quit"))
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// renderHost renders a single host
|
||||
func renderHost(host *models.Host) string {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(HostNameStyle.Render(host.Name))
|
||||
b.WriteString("\n")
|
||||
|
||||
details := fmt.Sprintf(" %s@%s:%d", host.Username, host.Hostname, host.Port)
|
||||
b.WriteString(HostDetailStyle.Render(details))
|
||||
|
||||
if len(host.Tags) > 0 {
|
||||
tags := formatTagsForTUI(host.Tags)
|
||||
b.WriteString(" " + TagStyle.Render(tags))
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// renderSelectedHost renders the selected host with highlight
|
||||
func renderSelectedHost(host *models.Host) string {
|
||||
hostText := renderHost(host)
|
||||
return SelectedStyle.Render(hostText)
|
||||
}
|
||||
|
||||
// formatTagsForTUI formats tags for TUI display
|
||||
func formatTagsForTUI(tags []string) string {
|
||||
if len(tags) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var formatted []string
|
||||
for _, tag := range tags {
|
||||
formatted = append(formatted, "["+tag+"]")
|
||||
}
|
||||
|
||||
return strings.Join(formatted, " ")
|
||||
}
|
||||
Reference in New Issue
Block a user