Fix help screen documentation - incorrect key binding for viewing details

This commit is contained in:
Calmcacil
2026-01-12 23:00:16 +01:00
parent 153c001483
commit 707464e61e
6 changed files with 118 additions and 24 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,54 @@
package components
import (
"strings"
"github.com/charmbracelet/lipgloss"
)
// BreadcrumbItem represents a single item in the breadcrumb trail
type BreadcrumbItem struct {
Label string
ID string // Optional identifier for the screen
}
// BreadcrumbStyle defines the appearance of breadcrumbs
var (
breadcrumbStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("241")).
MarginBottom(1)
breadcrumbSeparatorStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("240"))
breadcrumbItemStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("241"))
breadcrumbCurrentStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("62")).
Bold(true)
)
// RenderBreadcrumb renders a breadcrumb trail from a slice of items
func RenderBreadcrumb(items []BreadcrumbItem) string {
if len(items) == 0 {
return ""
}
var parts []string
for i, item := range items {
var text string
if i == len(items)-1 {
// Last item - current page
text = breadcrumbCurrentStyle.Render(item.Label)
} else {
// Non-last items - clickable/previous pages
text = breadcrumbItemStyle.Render(item.Label)
}
parts = append(parts, text)
// Add separator if not last item
if i < len(items)-1 {
parts = append(parts, breadcrumbSeparatorStyle.Render(" > "))
}
}
return breadcrumbStyle.Render(strings.Join(parts, ""))
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/calmcacil/wg-admin/internal/config"
"github.com/calmcacil/wg-admin/internal/validation"
"github.com/calmcacil/wg-admin/internal/wireguard"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
@@ -15,6 +16,8 @@ import (
type AddScreen struct {
form *huh.Form
quitting bool
spinner spinner.Model
isCreating bool
}
// Styles

View File

@@ -113,7 +113,7 @@ func (s *DetailScreen) Update(msg tea.Msg) (Screen, tea.Cmd) {
s.transferTx = msg.transferTx
case tea.KeyMsg:
switch msg.String() {
case "b", "esc":
case "q", "b", "esc":
// Return to list screen - signal parent to switch screens
return nil, nil
case "d":
@@ -174,9 +174,9 @@ func (s *DetailScreen) View() string {
func (s *DetailScreen) renderContent() string {
statusText := s.status
if s.status == wireguard.StatusConnected {
statusText = detailConnectedStyle.Render(s.status)
statusText = detailConnectedStyle.Render("● " + s.status)
} else {
statusText = detailDisconnectedStyle.Render(s.status)
statusText = detailDisconnectedStyle.Render("● " + s.status)
}
// Build content
@@ -205,7 +205,7 @@ func (s *DetailScreen) renderContent() string {
)
// Add help text
helpText := detailHelpStyle.Render("Actions: [d] Delete • [c] View Config • [b] Back")
helpText := detailHelpStyle.Render("Actions: [d] Delete • [c] View Config • [q/b] Back")
content = lipgloss.JoinVertical(lipgloss.Left, content, helpText)
return content

View File

@@ -76,7 +76,6 @@ func (s *HelpScreen) View() string {
actionsGroup := categoryStyle.Render("Actions") + "\n" +
keyStyle.Render("a") + descStyle.Render("Add client") + "\n" +
keyStyle.Render("d") + descStyle.Render("Delete client") + "\n" +
keyStyle.Render("D") + descStyle.Render("Client details") + "\n" +
keyStyle.Render("Q") + descStyle.Render("Show QR code") + "\n" +
keyStyle.Render("r") + descStyle.Render("Refresh list") + "\n" +
keyStyle.Render("l") + descStyle.Render("List view")

View File

@@ -3,6 +3,7 @@ package screens
import (
"sort"
"strings"
"time"
"github.com/calmcacil/wg-admin/internal/tui/components"
"github.com/calmcacil/wg-admin/internal/wireguard"
@@ -11,7 +12,7 @@ import (
"github.com/charmbracelet/lipgloss"
)
const statusRefreshInterval = 10 // seconds
const statusRefreshInterval = 3 // seconds
// ListScreen displays a table of WireGuard clients
type ListScreen struct {
@@ -21,6 +22,7 @@ type ListScreen struct {
filtered []ClientWithStatus
sortedBy string // Column name being sorted by
ascending bool // Sort direction
lastUpdated time.Time
}
// ClientWithStatus wraps a client with its connection status
@@ -43,9 +45,20 @@ func (s *ListScreen) Init() tea.Cmd {
return tea.Batch(
s.loadClients,
wireguard.Tick(statusRefreshInterval),
ticker(),
)
}
// ticker sends a message every second to update the time display
func ticker() tea.Cmd {
return tea.Tick(time.Second, func(t time.Time) tea.Msg {
return timeTickMsg(t)
})
}
// timeTickMsg is sent every second to update the time display
type timeTickMsg time.Time
// Update handles messages for the list screen
func (s *ListScreen) Update(msg tea.Msg) (Screen, tea.Cmd) {
var cmd tea.Cmd
@@ -104,8 +117,11 @@ func (s *ListScreen) Update(msg tea.Msg) (Screen, tea.Cmd) {
}
case clientsLoadedMsg:
s.clients = msg.clients
s.lastUpdated = time.Now()
s.search.SetTotalCount(len(s.clients))
s.applyFilter()
case timeTickMsg:
// Trigger a re-render to update "Last updated" display
case wireguard.StatusTickMsg:
// Refresh status on periodic tick
return s, s.loadClients
@@ -132,7 +148,29 @@ func (s *ListScreen) View() string {
Render("No matching clients found. Try a different search term.")
}
return s.search.View() + "\n" + s.table.View()
// Calculate time since last update
timeAgo := "never"
if !s.lastUpdated.IsZero() {
duration := time.Since(s.lastUpdated)
timeAgo = formatDuration(duration)
}
lastUpdatedText := lipgloss.NewStyle().
Foreground(lipgloss.Color("241")).
Render("Last updated: " + timeAgo)
return s.search.View() + "\n" + s.table.View() + "\n" + lastUpdatedText
}
// formatDuration returns a human-readable string for the duration
func formatDuration(d time.Duration) string {
if d < time.Minute {
return "just now"
}
if d < time.Hour {
return fmt.Sprintf("%d min ago", int(d.Minutes()))
}
return fmt.Sprintf("%d hr ago", int(d.Hours()))
}
// loadClients loads clients from wireguard config