From e0f8210c17458d7ccba77025ac83f4a2b331b6ee Mon Sep 17 00:00:00 2001 From: Calmcacil Date: Mon, 12 Jan 2026 19:17:51 +0100 Subject: [PATCH] fix: Fix 'q' key handling and connectivity status issues - Fix 'q' key in detail/add/help screens to return to list instead of quitting - Only quit application with 'q' when on main list screen - Fix parseHandshake to accumulate all time units instead of returning early This resolves handshake timing discrepancy with wg show output - Increase connection status threshold from 3 to 5 minutes Allows ~12 missed keepalive intervals (25s each) Improves connectivity status accuracy for peers with marginal connections --- .beads/issues.jsonl | 4 ++++ cmd/wg-tui/main.go | 7 +++++-- internal/wireguard/status.go | 20 +++++++++++++------- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index c3bf14f..8534283 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -12,6 +12,8 @@ {"id":"wg-admin-bay","title":"Implement real-time status checking","description":"Implement real-time connection status using 'wg show wg0' command. Check if client public key appears in peers list. Update status in table: Connected (active peer) or Disconnected (not in peers list). Add auto-refresh every 30 seconds using tea.Tick. Manual refresh with 'r' key.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:02:57.643693952+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T17:34:23.96887044+01:00","closed_at":"2026-01-12T17:34:23.96887044+01:00","close_reason":"Implemented real-time status checking functionality: Created status.go with GetClientStatus() and GetAllPeers() functions that parse 'wg show wg0' output to determine connection status. Added PeerStatus struct with PublicKey, Endpoint, AllowedIPs, LatestHandshake, TransferRx, TransferTx, and Status fields. Created tea_messages.go with Tick() command for auto-refresh and ManualRefresh() command for immediate refresh using tea.Tick and custom messages. Status is 'Connected' if handshake is within 3 minutes, otherwise 'Disconnected'.","dependencies":[{"issue_id":"wg-admin-bay","depends_on_id":"wg-admin-xum","type":"blocks","created_at":"2026-01-12T17:04:44.270454474+01:00","created_by":"Calmcacil"}]} {"id":"wg-admin-cwb","title":"Implement input validation functions","description":"Create robust validation functions: validate_client_name() (regex check for [a-zA-Z0-9_-]), validate_ip_availability(), validate_dns_servers(), validate_port_range(), validate_config_syntax(). Add validation before client creation and config changes.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T16:27:53.143579452+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T16:38:18.705584126+01:00","closed_at":"2026-01-12T16:38:18.705584126+01:00","close_reason":"Implemented all validation functions: validate_client_name(), validate_ip_availability(), validate_dns_servers(), validate_port_range(), validate_config_syntax(). Added validation calls in cmd_add and cmd_load_clients."} {"id":"wg-admin-dd2","title":"Implement client detail view","description":"Create detailed view for selected client showing name, IPs, public key, connection status, last handshake time, and transfer stats. Include copy to clipboard functionality and delete button with confirmation.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:03:30.290544009+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T17:54:27.63729296+01:00","closed_at":"2026-01-12T17:54:27.63729296+01:00","close_reason":"Implemented client detail view in internal/tui/screens/detail.go with all required features: displays client name, IPs, public key, connection status, handshake time, transfer stats; copy to clipboard (press 'c'), delete with confirmation (press 'd'), navigation back (press 'q' or 'esc'); styling with lipgloss in two-column layout; integrated with wireguard.GetClientStatus() for real-time status.","dependencies":[{"issue_id":"wg-admin-dd2","depends_on_id":"wg-admin-wf1","type":"blocks","created_at":"2026-01-12T17:04:52.968940596+01:00","created_by":"Calmcacil"}]} +{"id":"wg-admin-ddl","title":"Debug connectivity status showing Disconnected incorrectly","description":"Debug why connectivity status shows 'Disconnected' when client is actually connected. Investigate the finalizePeerStatus logic and the 3-minute threshold for connection status.","status":"closed","priority":1,"issue_type":"bug","owner":"Calmcacil@Raion","created_at":"2026-01-12T19:14:49.564914056+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T19:17:12.292551145+01:00","closed_at":"2026-01-12T19:17:12.292551145+01:00","close_reason":"Fixed q key handling to only quit from list screen, increased connection timeout threshold to 5 minutes"} +{"id":"wg-admin-dho","title":"Fix 'q' key to go back to list instead of quitting app","description":"Fix key handling in detail screen so 'q' returns to list instead of quitting the entire application. Currently pressing 'q' in detail view closes the app entirely.","status":"closed","priority":1,"issue_type":"bug","owner":"Calmcacil@Raion","created_at":"2026-01-12T19:14:49.562771127+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T19:17:12.284907523+01:00","closed_at":"2026-01-12T19:17:12.284907523+01:00","close_reason":"Fixed q key handling to only quit from list screen, increased connection timeout threshold to 5 minutes"} {"id":"wg-admin-e07","title":"Implement Dracula color scheme theme","description":"Add Dracula color scheme to the theme registry. Use the official Dracula color palette: Background #282A36, Primary (purple) #BD93F9, Success (green) #50FA7B, Warning (orange) #FFB86C, Error (red) #FF5555, Muted #6272A4.","status":"closed","priority":1,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T19:07:40.305419584+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T19:08:50.621690085+01:00","closed_at":"2026-01-12T19:08:50.621690085+01:00","close_reason":"Implemented Dracula and Everforest color schemes and set Everforest as default theme"} {"id":"wg-admin-ej7","title":"Implement color themes","description":"Add support for color themes using lipgloss. Create default theme with primary, success, warning, and error colors. Support theme switching through config file or environment variable.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:03:30.290145203+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T17:48:32.299616088+01:00","closed_at":"2026-01-12T17:48:32.299616088+01:00","close_reason":"Color themes implemented with Theme package. Three built-in themes (default, dark, light) with ColorScheme support. THEME config option and environment variable support added to config.go. Lipgloss styles use theme colors.","dependencies":[{"issue_id":"wg-admin-ej7","depends_on_id":"wg-admin-xum","type":"blocks","created_at":"2026-01-12T17:04:53.269323117+01:00","created_by":"Calmcacil"}]} {"id":"wg-admin-gp4","title":"Create Go TUI epic","description":"Epic: Convert wg-client-manager bash script to a modern, responsive Go TUI application using Bubble Tea framework. Provides better UX with interactive forms, real-time status updates, and intuitive keyboard navigation.","status":"closed","priority":1,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:03:30.286393088+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T18:06:58.972856627+01:00","closed_at":"2026-01-12T18:06:58.972856627+01:00","close_reason":"All Go TUI tasks completed across 6 phases:\n\nPhase 1 (Foundation): Project init, config system, TUI skeleton\nPhase 2 (Client List): Parsing, table, real-time status \nPhase 3 (Add Client): Form, key generation, config files\nPhase 4 (Detail/Delete): Detail view, delete functionality, QR codes\nPhase 5 (UX): Search, help screen, color themes\nPhase 6 (Backup/Restore): Backup operations, restore functionality\n\nImplementation details:\n- Total packages added: bubbletea, lipgloss, bubbles, huh, qrterminal\n- Files created: 25+ Go source files\n- All features implemented: CRUD clients, status checking, QR codes, themes, search, backup/restore\n- Build successful with ~6MB binary\n- All tasks closed and synced to remote\n\nApplication ready for testing and deployment."} @@ -28,8 +30,10 @@ {"id":"wg-admin-qpy","title":"Refactor installation into wg-install.sh","description":"Extract install logic from wireguard.sh into dedicated wg-install.sh script. Handle: dependency checks, package installation, firewall setup (nftables), server key generation, interface initialization, systemd service setup. Use interactive 'read' prompts for settings with 'WGI_' prefixed environment variable overrides.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T16:27:53.151817177+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T16:50:42.168393277+01:00","closed_at":"2026-01-12T16:50:42.168393277+01:00","close_reason":"Created wg-install.sh script with complete installation logic extracted from wireguard.sh. Script includes dependency checks, package installation, nftables firewall setup, server key generation, interface initialization, and systemd service setup. Uses interactive prompts with WGI_ prefixed environment variable overrides. All validation and error handling maintained with atomic operations and proper cleanup. Test suite (test-wg-install.sh) created with 35 tests all passing.","dependencies":[{"issue_id":"wg-admin-qpy","depends_on_id":"wg-admin-37o","type":"blocks","created_at":"2026-01-12T16:28:20.30398105+01:00","created_by":"Calmcacil"},{"issue_id":"wg-admin-qpy","depends_on_id":"wg-admin-wsk","type":"blocks","created_at":"2026-01-12T16:28:20.305872992+01:00","created_by":"Calmcacil"},{"issue_id":"wg-admin-qpy","depends_on_id":"wg-admin-0wc","type":"blocks","created_at":"2026-01-12T16:28:27.88358441+01:00","created_by":"Calmcacil"},{"issue_id":"wg-admin-qpy","depends_on_id":"wg-admin-cwb","type":"blocks","created_at":"2026-01-12T16:28:27.890595849+01:00","created_by":"Calmcacil"},{"issue_id":"wg-admin-qpy","depends_on_id":"wg-admin-2pl","type":"blocks","created_at":"2026-01-12T16:28:27.948214112+01:00","created_by":"Calmcacil"}]} {"id":"wg-admin-slj","title":"Refactor WireGuard scripts into modular architecture","description":"Refactor monolithic wireguard.sh into two separate scripts: wg-install.sh for initial setup, wg-client-manager for client operations. Use interactive 'read' prompts with 'WGI_' prefixed environment variable overrides. Add validation functions, security hardening, and remove all hardcoded sensitive information from repository.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T16:27:18.232667092+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T17:11:02.639140093+01:00","closed_at":"2026-01-12T17:11:02.639140093+01:00","close_reason":"Refactoring complete: wg-install.sh (921 lines) and wg-client-manager (545 lines) scripts have been created and are functional. wireguard.sh retained for backwards compatibility.","dependencies":[{"issue_id":"wg-admin-slj","depends_on_id":"wg-admin-abw","type":"blocks","created_at":"2026-01-12T16:28:21.930404739+01:00","created_by":"Calmcacil"},{"issue_id":"wg-admin-slj","depends_on_id":"wg-admin-qpy","type":"blocks","created_at":"2026-01-12T16:28:21.936380993+01:00","created_by":"Calmcacil"},{"issue_id":"wg-admin-slj","depends_on_id":"wg-admin-0wc","type":"blocks","created_at":"2026-01-12T16:28:21.983754904+01:00","created_by":"Calmcacil"}]} {"id":"wg-admin-tv6","title":"Add client delete functionality","description":"Implement delete client workflow with confirmation modal. Remove client config from server, delete client files, auto-backup before deletion, and reload WireGuard configuration.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:03:30.281557572+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T17:48:31.867154638+01:00","closed_at":"2026-01-12T17:48:31.867154638+01:00","close_reason":"Delete with confirmation modal implemented. Auto-backup before deletion, removes client configs and peer from WireGuard. Integrated with detail screen.","dependencies":[{"issue_id":"wg-admin-tv6","depends_on_id":"wg-admin-dd2","type":"blocks","created_at":"2026-01-12T17:04:36.207822184+01:00","created_by":"Calmcacil"}]} +{"id":"wg-admin-u4f","title":"Research handshake timing discrepancy in wg show vs app","description":"Research why wg show wg0 shows different handshake timing than the app. Found that parseHandshake function only parses first time unit (e.g., '14 hours' from '14 hours, 24 minutes, 40 seconds ago'), ignoring subsequent units causing significant time discrepancy.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T19:14:02.054350138+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T19:14:09.570320895+01:00","closed_at":"2026-01-12T19:14:09.570320895+01:00","close_reason":"Research complete - identified parseHandshake only parsing first time unit in compound expressions"} {"id":"wg-admin-wf1","title":"Create server and client config files","description":"Generate WireGuard configuration files for both server and client. Server config includes PublicKey and AllowedIPs. Client config includes PrivateKey, Address, DNS, Endpoint, and AllowedIPs. Use atomic writes (temp file + mv).","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:03:30.273615688+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T17:50:56.576191666+01:00","closed_at":"2026-01-12T17:50:56.576191666+01:00","close_reason":"Implemented WireGuard config file generation with atomic writes. Added GenerateServerConfig and GenerateClientConfig functions in internal/wireguard/config.go. Both functions use temp file + rename pattern for atomicity and set 0600 permissions.","dependencies":[{"issue_id":"wg-admin-wf1","depends_on_id":"wg-admin-o4o","type":"blocks","created_at":"2026-01-12T17:04:44.268995878+01:00","created_by":"Calmcacil"}]} {"id":"wg-admin-wjj","title":"Implement restore functionality","description":"Add restore capability to load backups from /etc/wg-admin/backups/. Include backup list view, restore confirmation, and pre-restore safety backup. Handle missing backups gracefully.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:03:30.29166861+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T18:06:07.558190874+01:00","closed_at":"2026-01-12T18:06:07.558190874+01:00","close_reason":"Restore functionality implemented in internal/backup/restore.go. ListBackups, RestoreBackup with validation. Restore screen with selectable table. Pre-restore safety backup. ReloadWireGuard integration. Successfully completes Phase 6.","dependencies":[{"issue_id":"wg-admin-wjj","depends_on_id":"wg-admin-11o","type":"blocks","created_at":"2026-01-12T17:04:36.234546234+01:00","created_by":"Calmcacil"}]} +{"id":"wg-admin-wmn","title":"Fix parseHandshake to correctly parse compound time expressions","description":"Fix parseHandshake function in status.go to correctly parse compound time expressions like '14 hours, 24 minutes, 40 seconds ago'. Currently only parses first time unit, causing app to show incorrect handshake timing that differs from wg show output.","status":"closed","priority":1,"issue_type":"bug","owner":"Calmcacil@Raion","created_at":"2026-01-12T19:14:02.056632886+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T19:14:41.087635401+01:00","closed_at":"2026-01-12T19:14:41.087635401+01:00","close_reason":"Fixed parseHandshake to accumulate all time units instead of returning early"} {"id":"wg-admin-wod","title":"Create add client form with huh","description":"Implement form for adding new WireGuard clients using the huh library. Include fields for client name, DNS servers, and PSK toggle. Add validation for client name format, IP availability, and DNS format.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:03:30.272758265+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T17:56:47.982894819+01:00","closed_at":"2026-01-12T17:56:47.982894819+01:00","close_reason":"Implemented add client form using huh library with validation for client name, DNS servers, and PSK toggle. Added CreateClient function in wireguard package for key generation, IP allocation, config creation, QR code generation, and peer interface management. Updated main.go to handle 'a' key for add screen navigation.","dependencies":[{"issue_id":"wg-admin-wod","depends_on_id":"wg-admin-xum","type":"blocks","created_at":"2026-01-12T17:04:26.667835195+01:00","created_by":"Calmcacil"}]} {"id":"wg-admin-wsk","title":"Add configuration validation and syntax checking","description":"Implement validate_config_syntax() to check WireGuard config format before applying: verify [Interface] and [Peer] sections, check key format (44 base64 characters), validate IP addresses and CIDR notation, validate DNS format, ensure no duplicate public keys.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T16:27:53.159692055+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T16:38:35.611667005+01:00","closed_at":"2026-01-12T16:38:35.611667005+01:00","close_reason":"validate_config_syntax() implemented in previous task with full WireGuard config format validation: [Interface]/[Peer] sections, key format (44 base64 chars), IP/CIDR notation, DNS format, duplicate public key detection. Integrated into cmd_load_clients."} {"id":"wg-admin-xni","title":"Update documentation with theme options","description":"Update README.md and documentation to include the new theme options (dracula, everforest) and how to switch themes using the THEME environment variable.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T19:07:40.300443242+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T19:10:22.220478311+01:00","closed_at":"2026-01-12T19:10:22.220478311+01:00","close_reason":"Documentation updated with theme options, build successful, theme switching logic verified"} diff --git a/cmd/wg-tui/main.go b/cmd/wg-tui/main.go index c40ae58..7fc6c70 100644 --- a/cmd/wg-tui/main.go +++ b/cmd/wg-tui/main.go @@ -47,10 +47,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: - switch msg.String() { - case "q", "ctrl+c": + // Only quit on 'q' when on list screen + // Other screens handle 'q' to navigate back + if _, ok := m.currentScreen.(*screens.ListScreen); ok && (msg.String() == "q" || msg.String() == "ctrl+c") { m.quitting = true return m, tea.Quit + } + switch msg.String() { case "?": // Switch to help screen m.previousScreen = m.currentScreen diff --git a/internal/wireguard/status.go b/internal/wireguard/status.go index 4cba2cb..5313853 100644 --- a/internal/wireguard/status.go +++ b/internal/wireguard/status.go @@ -143,8 +143,9 @@ func finalizePeerStatus(peer *PeerStatus, handshake string, transfer string) Pee // Determine status based on handshake if handshake != "" { peer.LatestHandshake = parseHandshake(handshake) - // Peer is considered connected if handshake is recent (within 3 minutes) - if time.Since(peer.LatestHandshake) < 3*time.Minute { + // 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 { peer.Status = StatusConnected } else { peer.Status = StatusDisconnected @@ -159,27 +160,28 @@ func finalizePeerStatus(peer *PeerStatus, handshake string, transfer string) Pee // parseHandshake converts handshake string to time.Time func parseHandshake(handshake string) time.Time { now := time.Now() + var totalDuration time.Duration parts := strings.Fields(handshake) for i, part := range parts { if strings.HasSuffix(part, "second") || strings.HasSuffix(part, "seconds") { if val, err := strconv.Atoi(strings.TrimSuffix(part, "s")); err == nil { - return now.Add(-time.Duration(val) * time.Second) + totalDuration += time.Duration(val) * time.Second } } if strings.HasSuffix(part, "minute") || strings.HasSuffix(part, "minutes") { if val, err := strconv.Atoi(strings.TrimSuffix(part, "s")); err == nil { - return now.Add(-time.Duration(val) * time.Minute) + totalDuration += time.Duration(val) * time.Minute } } if strings.HasSuffix(part, "hour") || strings.HasSuffix(part, "hours") { if val, err := strconv.Atoi(strings.TrimSuffix(part, "s")); err == nil { - return now.Add(-time.Duration(val) * time.Hour) + totalDuration += time.Duration(val) * time.Hour } } if strings.HasSuffix(part, "day") || strings.HasSuffix(part, "days") { if val, err := strconv.Atoi(strings.TrimSuffix(part, "s")); err == nil { - return now.Add(-time.Duration(val) * 24 * time.Hour) + totalDuration += time.Duration(val) * 24 * time.Hour } } // Handle "ago" word @@ -188,7 +190,11 @@ func parseHandshake(handshake string) time.Time { } } - return now.Add(-time.Hour) // Default to 1 hour ago if parsing fails + if totalDuration == 0 { + return now.Add(-time.Hour) // Default to 1 hour ago if parsing fails + } + + return now.Add(-totalDuration) } // CheckInterface verifies if the WireGuard interface exists and is accessible