Compare commits
2 Commits
17f4d52c8a
...
f0e26e4a0a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0e26e4a0a | ||
|
|
dd62458515 |
File diff suppressed because one or more lines are too long
17
README.md
17
README.md
@@ -223,6 +223,23 @@ Then run:
|
||||
sudo ~/wireguard.sh load-clients
|
||||
```
|
||||
|
||||
## Text Selection & Copying
|
||||
|
||||
To copy client configurations or other text from the terminal UI:
|
||||
|
||||
### Text Selection
|
||||
- Hold **SHIFT key** while dragging your mouse with the left button
|
||||
- This bypasses TUI mouse handling and enables your terminal's native text selection
|
||||
- Then use your terminal's copy shortcut:
|
||||
- **Linux/WSL**: Ctrl+Shift+C (or terminal-specific shortcut)
|
||||
- **macOS**: Cmd+C
|
||||
- **Windows**: Click right (or use terminal copy)
|
||||
|
||||
### Copy Buttons (when available)
|
||||
- Some modals may have explicit copy buttons (e.g., "C" to copy config)
|
||||
- These work when clipboard API is available (native Linux, macOS, WSL)
|
||||
- Over SSH requires X11 forwarding (`ssh -X`) for clipboard access
|
||||
|
||||
## Client Setup
|
||||
|
||||
### Importing the config
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
@@ -263,7 +264,7 @@ func (m *SearchModel) HighlightMatches(value string) string {
|
||||
Bold(true)
|
||||
|
||||
before := value[:index]
|
||||
match := value[index+len(query)]
|
||||
match := value[index : index+len(query)]
|
||||
after := value[index+len(query):]
|
||||
|
||||
return lipgloss.JoinHorizontal(
|
||||
|
||||
@@ -152,7 +152,9 @@ func (s *DetailScreen) renderContent() string {
|
||||
|
||||
statusText := s.status
|
||||
if s.status == wireguard.StatusConnected {
|
||||
statusText = theme.StyleSuccess.Bold(true).Render("● " + s.status)
|
||||
duration := time.Since(s.lastHandshake)
|
||||
quality := wireguard.CalculateQuality(duration)
|
||||
statusText = theme.StyleSuccess.Bold(true).Render("● " + s.status + " (" + quality + ")")
|
||||
} else {
|
||||
statusText = theme.StyleError.Bold(true).Render("● " + s.status)
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ func (s *HelpScreen) View() string {
|
||||
// Breadcrumb: Help
|
||||
breadcrumb := components.RenderBreadcrumb([]components.BreadcrumbItem{{Label: "Help", ID: "help"}})
|
||||
|
||||
|
||||
// Styles
|
||||
borderStyle := lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.RoundedBorder()).
|
||||
@@ -90,6 +89,11 @@ func (s *HelpScreen) View() string {
|
||||
keyStyle.Render("/") + descStyle.Render("Search") + "\n" +
|
||||
keyStyle.Render("q") + descStyle.Render("Quit")
|
||||
|
||||
copyGroup := categoryStyle.Render("Text Selection & Copy") + "\n" +
|
||||
keyStyle.Render("SHIFT+drag") + descStyle.Render("Select text") + "\n" +
|
||||
keyStyle.Render("Ctrl+Shift+C") + descStyle.Render("Copy (Linux)") + "\n" +
|
||||
keyStyle.Render("Cmd+C") + descStyle.Render("Copy (macOS)")
|
||||
|
||||
// Two-column layout
|
||||
leftColumn := lipgloss.JoinVertical(lipgloss.Left,
|
||||
navigationGroup,
|
||||
@@ -99,6 +103,8 @@ func (s *HelpScreen) View() string {
|
||||
|
||||
rightColumn := lipgloss.JoinVertical(lipgloss.Left,
|
||||
otherGroup,
|
||||
"",
|
||||
copyGroup,
|
||||
)
|
||||
|
||||
content := lipgloss.JoinHorizontal(lipgloss.Top, leftColumn, " ", rightColumn)
|
||||
|
||||
@@ -30,14 +30,20 @@ var (
|
||||
once sync.Once
|
||||
|
||||
// Global styles that can be used throughout the application
|
||||
StylePrimary lipgloss.Style
|
||||
StyleSuccess lipgloss.Style
|
||||
StyleWarning lipgloss.Style
|
||||
StyleError lipgloss.Style
|
||||
StyleMuted lipgloss.Style
|
||||
StyleTitle lipgloss.Style
|
||||
StyleSubtitle lipgloss.Style
|
||||
StyleHelpKey lipgloss.Style
|
||||
StylePrimary lipgloss.Style
|
||||
StyleSuccess lipgloss.Style
|
||||
StyleWarning lipgloss.Style
|
||||
StyleError lipgloss.Style
|
||||
StyleMuted lipgloss.Style
|
||||
StyleTitle lipgloss.Style
|
||||
StyleSubtitle lipgloss.Style
|
||||
StyleHelpKey lipgloss.Style
|
||||
StyleValue lipgloss.Style
|
||||
StyleDimmed lipgloss.Style
|
||||
StyleTableHeader lipgloss.Style
|
||||
StyleTableSelected lipgloss.Style
|
||||
StyleBorder lipgloss.Color
|
||||
StyleBackground lipgloss.Color
|
||||
)
|
||||
|
||||
// DefaultTheme is the standard blue-based theme
|
||||
@@ -176,6 +182,33 @@ func ApplyTheme(theme *Theme) {
|
||||
StyleHelpKey = lipgloss.NewStyle().
|
||||
Foreground(theme.Scheme.Primary).
|
||||
Bold(true)
|
||||
|
||||
// Value style for content values
|
||||
StyleValue = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("255"))
|
||||
|
||||
// Dimmed style for overlay content
|
||||
StyleDimmed = lipgloss.NewStyle().
|
||||
Foreground(theme.Scheme.Muted)
|
||||
|
||||
// Table header style
|
||||
StyleTableHeader = lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.NormalBorder()).
|
||||
BorderForeground(lipgloss.Color("240")).
|
||||
BorderBottom(true).
|
||||
Bold(true)
|
||||
|
||||
// Table selected style
|
||||
StyleTableSelected = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("229")).
|
||||
Background(lipgloss.Color("57")).
|
||||
Bold(false)
|
||||
|
||||
// Border color
|
||||
StyleBorder = lipgloss.Color("240")
|
||||
|
||||
// Background color for modals
|
||||
StyleBackground = lipgloss.Color("235")
|
||||
}
|
||||
|
||||
// GetThemeNames returns a list of available theme names
|
||||
|
||||
@@ -15,6 +15,15 @@ const (
|
||||
StatusConnected = "Connected"
|
||||
// StatusDisconnected indicates a peer is not connected
|
||||
StatusDisconnected = "Disconnected"
|
||||
|
||||
// QualityExcellent indicates handshake was very recent (< 30s)
|
||||
QualityExcellent = "Excellent"
|
||||
// QualityGood indicates handshake was recent (< 2m)
|
||||
QualityGood = "Good"
|
||||
// QualityFair indicates handshake was acceptable (< 5m)
|
||||
QualityFair = "Fair"
|
||||
// QualityPoor indicates handshake was old (> 5m)
|
||||
QualityPoor = "Poor"
|
||||
)
|
||||
|
||||
// PeerStatus represents the status of a WireGuard peer
|
||||
@@ -25,7 +34,8 @@ type PeerStatus struct {
|
||||
LatestHandshake time.Time `json:"latest_handshake"`
|
||||
TransferRx string `json:"transfer_rx"`
|
||||
TransferTx string `json:"transfer_tx"`
|
||||
Status string `json:"status"` // "Connected" or "Disconnected"
|
||||
Status string `json:"status"` // "Connected" or "Disconnected"
|
||||
Quality string `json:"quality,omitempty"` // "Excellent", "Good", "Fair", "Poor" (if connected)
|
||||
}
|
||||
|
||||
// GetClientStatus checks if a specific client is connected
|
||||
@@ -119,6 +129,20 @@ func parsePeersOutput(output string) []PeerStatus {
|
||||
return peers
|
||||
}
|
||||
|
||||
// CalculateQuality returns the connection quality based on handshake time
|
||||
func CalculateQuality(timeSinceHandshake time.Duration) string {
|
||||
if timeSinceHandshake < 30*time.Second {
|
||||
return QualityExcellent
|
||||
}
|
||||
if timeSinceHandshake < 2*time.Minute {
|
||||
return QualityGood
|
||||
}
|
||||
if timeSinceHandshake < 5*time.Minute {
|
||||
return QualityFair
|
||||
}
|
||||
return QualityPoor
|
||||
}
|
||||
|
||||
// finalizePeerStatus determines the peer's status based on handshake time
|
||||
func finalizePeerStatus(peer *PeerStatus, handshake string, transfer string) PeerStatus {
|
||||
peer.TransferRx = ""
|
||||
@@ -140,13 +164,16 @@ func finalizePeerStatus(peer *PeerStatus, handshake string, transfer string) Pee
|
||||
}
|
||||
}
|
||||
|
||||
// Determine status based on handshake
|
||||
// Determine status and quality based on handshake
|
||||
if handshake != "" {
|
||||
peer.LatestHandshake = parseHandshake(handshake)
|
||||
timeSinceHandshake := time.Since(peer.LatestHandshake)
|
||||
|
||||
// Peer is considered connected if handshake is recent (within 5 minutes)
|
||||
// This allows for ~12 missed keepalive intervals (at 25 seconds each)
|
||||
if time.Since(peer.LatestHandshake) < 5*time.Minute {
|
||||
if timeSinceHandshake < 5*time.Minute {
|
||||
peer.Status = StatusConnected
|
||||
peer.Quality = calculateQuality(timeSinceHandshake)
|
||||
} else {
|
||||
peer.Status = StatusDisconnected
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user