diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 8bc473b..3c7ebf1 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,7 +1,7 @@ {"id":"wg-admin-0va","title":"Add configuration backup and rollback","description":"Create backup functions: backup_config() (creates timestamped backups), restore_config(), auto-backup before destructive operations (add, remove, install). Store backups in /etc/wg-admin/backups/ with retention policy (e.g., keep last 10).","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T16:27:53.161279119+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T16:44:03.357384383+01:00","closed_at":"2026-01-12T16:44:03.357384383+01:00","close_reason":"Implemented configuration backup and rollback functionality: added backup_config() function (creates timestamped backups in /etc/wg-admin/backups/), restore_config() function (interactive restore from backup), apply_retention_policy() (keeps last 10 backups), and auto-backup before destructive operations (install, add, remove commands)."} {"id":"wg-admin-0wc","title":"Remove hardcoded sensitive information","description":"Identify and remove all hardcoded sensitive values from wireguard.sh. Replace with config file reads. Remove: SERVER_DOMAIN (velkhana.calmcacil.dev), VPN_IP_RANGES, any other identifiable information. Document config file structure in README.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T16:27:53.158448895+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T16:43:08.224554317+01:00","closed_at":"2026-01-12T16:43:08.224554317+01:00","close_reason":"Removed all hardcoded sensitive information: SERVER_DOMAIN, VPN_IPV4_RANGE, VPN_IPV6_RANGE, DNS_SERVERS now configurable via /etc/wg-admin/config.conf. Added load_config() function. Created config.example template. Updated README with configuration documentation. All IP ranges in script now reference config variables."} {"id":"wg-admin-11o","title":"Implement backup operations","description":"Create backup functionality that saves WireGuard configs, client configs, and metadata to /etc/wg-admin/backups/ with timestamp. Set proper permissions (600 for sensitive files).","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:03:30.288606376+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T18:06:07.405162631+01:00","closed_at":"2026-01-12T18:06:07.405162631+01:00","close_reason":"Backup operations implemented in internal/backup/backup.go. CreateBackup with timestamp, retention policy (last 10). ListBackups, RestoreBackup with validation. ReloadWireGuard after restore. Integration with client operations.","dependencies":[{"issue_id":"wg-admin-11o","depends_on_id":"wg-admin-wf1","type":"blocks","created_at":"2026-01-12T17:04:36.19397874+01:00","created_by":"Calmcacil"}]} -{"id":"wg-admin-1b0","title":"Add loading spinners for async operations (client creation, restore, QR generation)","description":"Add visual loading feedback using bubbles/spinner for long-running operations like client creation, backup restore, and QR code generation. Users cannot tell if application is working or frozen during these operations.","status":"open","priority":0,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:39:36.103626542+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T21:39:36.103626542+01:00"} +{"id":"wg-admin-1b0","title":"Add loading spinners for async operations (client creation, restore, QR generation)","description":"Add visual loading feedback using bubbles/spinner for long-running operations like client creation, backup restore, and QR code generation. Users cannot tell if application is working or frozen during these operations.","status":"in_progress","priority":0,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:39:36.103626542+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T22:59:02.276860984+01:00"} {"id":"wg-admin-1b9","title":"Update documentation for refactored scripts","description":"Update README.md and all documentation to reflect new architecture. Document: wg-install.sh usage (interactive prompts, WGI_ env vars), wg-client-manager commands (add, remove, list, show, qr), environment variable reference, security hardening features, backup/restore procedures. Update examples with new patterns.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T16:33:43.749727154+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T17:13:12.828613341+01:00","closed_at":"2026-01-12T17:13:12.828613341+01:00","close_reason":"Documentation updated on main branch. README.md reflects new wg-install.sh and wg-client-manager scripts, WGI_ environment variables, and all usage patterns.","dependencies":[{"issue_id":"wg-admin-1b9","depends_on_id":"wg-admin-slj","type":"blocks","created_at":"2026-01-12T16:33:56.00899014+01:00","created_by":"Calmcacil"}]} {"id":"wg-admin-2oj","title":"Add screen transition animations for more polished UX","description":"Add brief fade or slide animations when switching screens for more polished feel. Current screen transitions are instant without feedback. Consider lipgloss positioning and tick-based transitions.","status":"open","priority":3,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:40:48.821352971+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T21:40:48.821352971+01:00"} {"id":"wg-admin-2pl","title":"Improve nftables firewall configuration","description":"Enhance firewall rules based on best practices: add TCP MSS clamping for MTU issues, add connection tracking bypass (notrack) for WireGuard traffic, implement proper rate limiting, ensure ICMPv6 neighbor discovery is allowed, validate rules before applying with nft check.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T16:27:53.15783619+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T16:37:11.050440729+01:00","closed_at":"2026-01-12T16:37:11.050440729+01:00","close_reason":"Improved nftables firewall configuration with TCP MSS clamping (1360), connection tracking bypass (notrack) for WireGuard UDP traffic, rate limiting for SSH (3/min) and WireGuard (10/s), ensured ICMPv6 neighbor discovery (including nd-router-* messages), and added nft check validation before applying rules."} @@ -12,7 +12,7 @@ {"id":"wg-admin-4ji","title":"Initialize Go module and project structure","description":"Initialize Go project with go mod init. Create directory structure following plan: cmd/, internal/config, internal/wireguard, internal/tui (screens, components, theme), internal/validation, internal/backup. Add README with project setup instructions.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:02:57.197740013+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T17:20:34.471816058+01:00","closed_at":"2026-01-12T17:20:34.471816058+01:00","close_reason":"Go module initialized, directory structure created (cmd/, internal/ subdirectories), dependencies added (bubbletea, lipgloss, bubbles, huh, qrterminal), basic TUI skeleton with Model-Update-View pattern implemented. Root check added. Builds successfully.","dependencies":[{"issue_id":"wg-admin-4ji","depends_on_id":"wg-admin-gp4","type":"blocks","created_at":"2026-01-12T17:04:26.670875524+01:00","created_by":"Calmcacil"}]} {"id":"wg-admin-55x","title":"Add connection quality indicators (Excellent/Good/Fair/Poor) based on handshake time","description":"Currently only shows Connected/Disconnected. Add connection quality indicators based on handshake recency: Excellent (\u003c30s), Good (\u003c2m), Fair (\u003c5m), Poor (\u003e5m). This provides more informative feedback about connection health.","status":"open","priority":2,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:40:23.26708921+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T21:40:23.26708921+01:00"} {"id":"wg-admin-69b","title":"Implement WireGuard client parsing","description":"Parse WireGuard client configuration files from /etc/wireguard/conf.d/client-*.conf. Extract client name, IPv4, IPv6, public key, and PSK status. Create Client struct. Handle file read errors and malformed configs. Validate config syntax.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:02:57.199808074+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T17:39:44.913242962+01:00","closed_at":"2026-01-12T17:39:44.913242962+01:00","close_reason":"Client struct created in internal/wireguard/client.go with fields: Name, IPv4, IPv6, PublicKey, HasPSK, ConfigPath. ParseClientConfig() parses [Peer] sections from config files. ListClients() scans /etc/wireguard/conf.d/ for client-*.conf files. Handles errors gracefully.","dependencies":[{"issue_id":"wg-admin-69b","depends_on_id":"wg-admin-4fb","type":"blocks","created_at":"2026-01-12T17:04:44.265421971+01:00","created_by":"Calmcacil"}]} -{"id":"wg-admin-7ra","title":"Fix help screen documentation - incorrect key binding for viewing details","description":"Help screen shows '[D]' key for viewing client details, but actual access is via 'Enter' key. Update help documentation to show correct keybinding.","status":"open","priority":0,"issue_type":"bug","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:39:36.098716455+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T21:39:36.098716455+01:00"} +{"id":"wg-admin-7ra","title":"Fix help screen documentation - incorrect key binding for viewing details","description":"Help screen shows '[D]' key for viewing client details, but actual access is via 'Enter' key. Update help documentation to show correct keybinding.","status":"closed","priority":0,"issue_type":"bug","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:39:36.098716455+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T23:00:16.297251855+01:00","closed_at":"2026-01-12T23:00:16.297251855+01:00","close_reason":"Fixed help screen to remove incorrect 'D' key for client details. Enter key is already documented in Navigation section as 'Select'."} {"id":"wg-admin-abw","title":"Create wg-client-manager script","description":"Create new wg-client-manager script for client operations: add, remove, list, show, qr. Implement proper command parsing, use interactive 'read' with 'WGI_' environment variable overrides, call validation functions, use atomic config updates.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T16:27:53.150007325+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T16:48:38.86400169+01:00","closed_at":"2026-01-12T16:48:38.86400169+01:00","close_reason":"Created wg-client-manager script with all required commands (add, remove, list, show, qr). Implements interactive prompts with WGI_ environment variable overrides, uses validation functions, and performs atomic config updates.","dependencies":[{"issue_id":"wg-admin-abw","depends_on_id":"wg-admin-cwb","type":"blocks","created_at":"2026-01-12T16:28:20.280054863+01:00","created_by":"Calmcacil"},{"issue_id":"wg-admin-abw","depends_on_id":"wg-admin-37o","type":"blocks","created_at":"2026-01-12T16:28:20.299310073+01:00","created_by":"Calmcacil"},{"issue_id":"wg-admin-abw","depends_on_id":"wg-admin-lzl","type":"blocks","created_at":"2026-01-12T16:28:20.300924186+01:00","created_by":"Calmcacil"},{"issue_id":"wg-admin-abw","depends_on_id":"wg-admin-wsk","type":"blocks","created_at":"2026-01-12T16:28:20.354270061+01:00","created_by":"Calmcacil"},{"issue_id":"wg-admin-abw","depends_on_id":"wg-admin-0va","type":"blocks","created_at":"2026-01-12T16:28:21.926811217+01:00","created_by":"Calmcacil"}]} {"id":"wg-admin-az7","title":"Fix client details navigation and improve deletion confirmation UX","description":"\n# Issues to Fix\n\n## 1. Navigation Key Binding Issues\n- **Problem**: 'q' key for going back from client details screen doesn't work properly\n- **Solution**: \n - Change 'q' key to 'b' for back navigation (more intuitive)\n - Add 'esc' key binding for back navigation (in addition to 'b')\n - Update help text to reflect new key bindings\n\n## 2. Deletion Confirmation UX Improvements\n- **Problem**: Current confirmation modal has poor formatting and lacks safety checks\n- **Solution**:\n - **Improve modal formatting**: Better visual styling, clearer layout\n - **Add name verification**: Require user to type the client's name before confirming deletion (prevents accidental deletions)\n - Better visual feedback for selected option (Yes/No)\n\n# Files to Modify\n\n1. **internal/tui/screens/detail.go**\n - Line 124: Change key binding from 'q' to 'b'\n - Line 124: Add 'esc' to back navigation (ensure it works)\n - Line 199: Update help text to show new key bindings\n - Lines 127-134: Replace simple confirm modal with name-verification modal\n\n2. **internal/tui/components/confirm.go** (or create new component)\n - Improve modal styling and formatting\n - Add text input field for client name verification\n - Update key bindings for name input field\n\n# Implementation Notes\n\n- The confirm modal currently uses simple Yes/No with arrow keys\n- New approach: Show \"Type the client name to confirm: [input field]\"\n- Only enable confirm button when input matches client name exactly\n- Case-sensitive name matching for safety\n- Add clear visual feedback (red for danger, green for match)\n","status":"closed","priority":0,"issue_type":"bug","owner":"Calmcacil@Raion","created_at":"2026-01-12T22:13:28.830300356+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T22:16:47.251848171+01:00","closed_at":"2026-01-12T22:16:47.251848171+01:00","close_reason":"Fixed navigation key bindings (changed 'q' to 'b', added 'esc') and created improved deletion confirmation modal with name verification for safety."} {"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"}]} @@ -27,11 +27,11 @@ {"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."} {"id":"wg-admin-gw9","title":"Add search and filter clients","description":"Implement client search functionality with keyboard shortcut (/). Allow filtering by client name, IP address, or status. Highlight matching results in real-time as user types.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:03:30.285733479+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T18:06:07.246176563+01:00","closed_at":"2026-01-12T18:06:07.246176563+01:00","close_reason":"Search component implemented in internal/tui/components/search.go. Real-time filtering by name, IP, or status with '/' activation. Filter types cycle with Tab. Match count display. Integrated with list screen to dynamically update client table.","dependencies":[{"issue_id":"wg-admin-gw9","depends_on_id":"wg-admin-xum","type":"blocks","created_at":"2026-01-12T17:04:36.200521151+01:00","created_by":"Calmcacil"}]} {"id":"wg-admin-hd4","title":"Add keyboard shortcuts help","description":"Create help screen displaying all keyboard shortcuts. Show on '?' key press or in status bar. Include shortcuts for navigation (j/k, arrows), actions (a=add, d=delete, q=quit), and help (?).","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:03:30.283054325+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T17:48:32.156575557+01:00","closed_at":"2026-01-12T17:48:32.156575557+01:00","close_reason":"Help screen implemented with keyboard shortcuts table organized by category. Shows on ? key or status bar. Navigation, actions, and other shortcuts documented with lipgloss styling.","dependencies":[{"issue_id":"wg-admin-hd4","depends_on_id":"wg-admin-xum","type":"blocks","created_at":"2026-01-12T17:04:53.117669255+01:00","created_by":"Calmcacil"}]} -{"id":"wg-admin-he6","title":"Reduce status refresh interval from 10 to 3 seconds for better real-time awareness","description":"Currently connection status refreshes every 10 seconds which is too slow for real-time awareness. Reduce interval to 3 seconds and add 'Last updated: X ago' indicator so users know when data was last refreshed.","status":"open","priority":1,"issue_type":"bug","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:40:04.503637878+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T21:40:04.503637878+01:00"} -{"id":"wg-admin-hln","title":"Improve empty state messages with actionable guidance and call-to-action","description":"When no clients exist or search returns no results, provide clear guidance on next steps. Add tips like 'Press [a] to add your first client', 'Check spelling', 'Try shorter search term'. Make empty states welcoming and helpful.","status":"open","priority":1,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:40:04.504693809+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T21:40:04.504693809+01:00"} -{"id":"wg-admin-hzl","title":"Add visual status icons (● Online/● Offline) for better scanning","description":"Connection status is currently just text ('Connected'/'Disconnected'). Add visual indicators with colored circles/icons for faster scanning. Green ● for connected, red ● for disconnected. This improves visual hierarchy and scanability.","status":"open","priority":1,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:40:04.554974884+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T21:40:04.554974884+01:00"} -{"id":"wg-admin-iqs","title":"Add navigation breadcrumbs to all screens for context","description":"Currently no breadcrumbs or navigation context in screens. Users can't tell where they are in the navigation hierarchy (list \u003e details, add \u003e form). Add breadcrumb at top of each screen showing navigation path.","status":"open","priority":2,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:40:23.210874178+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T21:40:23.210874178+01:00"} -{"id":"wg-admin-jho","title":"Integrate theme system across all screens","description":"The theme system (internal/tui/theme/theme.go) with StylePrimary, StyleSuccess, StyleError is fully implemented but all screens hardcode colors instead. Replace hardcoded colors with theme references for consistency and to enable theme switching functionality.","status":"open","priority":0,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:39:36.098683097+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T21:39:36.098683097+01:00"} +{"id":"wg-admin-he6","title":"Reduce status refresh interval from 10 to 3 seconds for better real-time awareness","description":"Currently connection status refreshes every 10 seconds which is too slow for real-time awareness. Reduce interval to 3 seconds and add 'Last updated: X ago' indicator so users know when data was last refreshed.","status":"in_progress","priority":1,"issue_type":"bug","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:40:04.503637878+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T22:58:59.044822026+01:00"} +{"id":"wg-admin-hln","title":"Improve empty state messages with actionable guidance and call-to-action","description":"When no clients exist or search returns no results, provide clear guidance on next steps. Add tips like 'Press [a] to add your first client', 'Check spelling', 'Try shorter search term'. Make empty states welcoming and helpful.","status":"in_progress","priority":1,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:40:04.504693809+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T22:58:57.083725632+01:00"} +{"id":"wg-admin-hzl","title":"Add visual status icons (● Online/● Offline) for better scanning","description":"Connection status is currently just text ('Connected'/'Disconnected'). Add visual indicators with colored circles/icons for faster scanning. Green ● for connected, red ● for disconnected. This improves visual hierarchy and scanability.","status":"in_progress","priority":1,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:40:04.554974884+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T22:59:07.349352101+01:00"} +{"id":"wg-admin-iqs","title":"Add navigation breadcrumbs to all screens for context","description":"Currently no breadcrumbs or navigation context in screens. Users can't tell where they are in the navigation hierarchy (list \u003e details, add \u003e form). Add breadcrumb at top of each screen showing navigation path.","status":"in_progress","priority":2,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:40:23.210874178+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T22:59:07.202238466+01:00"} +{"id":"wg-admin-jho","title":"Integrate theme system across all screens","description":"The theme system (internal/tui/theme/theme.go) with StylePrimary, StyleSuccess, StyleError is fully implemented but all screens hardcode colors instead. Replace hardcoded colors with theme references for consistency and to enable theme switching functionality.","status":"in_progress","priority":0,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:39:36.098683097+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T22:58:51.807174004+01:00"} {"id":"wg-admin-k3q","title":"Implement Everforest color scheme theme","description":"Add Everforest color scheme to the theme registry. Use the Everforest dark variant: Background #272e33, Primary (blue) #7fbbb3, Success (green) #a7c080, Warning (yellow) #dbbc7f, Error (red) #e67e80, Muted #414b50.","status":"closed","priority":1,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T19:07:40.303542104+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T19:08:50.629948797+01:00","closed_at":"2026-01-12T19:08:50.629948797+01:00","close_reason":"Implemented Dracula and Everforest color schemes and set Everforest as default theme"} {"id":"wg-admin-ka8","title":"Generate QR codes for clients","description":"Generate ANSI-colored QR codes from client configs using qrterminal library. Support both inline and fullscreen QR display modes. Handle terminal resize events for optimal QR rendering.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T17:03:30.273562645+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T17:48:32.013388859+01:00","closed_at":"2026-01-12T17:48:32.013388859+01:00","close_reason":"QR code display implemented using qrterminal. Supports inline and fullscreen modes with f toggle. Handles terminal resize events. Returns to list on q/Esc.","dependencies":[{"issue_id":"wg-admin-ka8","depends_on_id":"wg-admin-wf1","type":"blocks","created_at":"2026-01-12T17:04:36.203581002+01:00","created_by":"Calmcacil"}]} {"id":"wg-admin-kfs","title":"Create configuration file format for WireGuard settings","description":"Design and implement /etc/wg-admin/config file to replace hardcoded values. Include: SERVER_DOMAIN, WG_PORT, VPN_IPV4_RANGE, VPN_IPV6_RANGE, WG_INTERFACE, DNS_SERVERS, and other configurable parameters. Support both file-based and environment variable override.","status":"closed","priority":2,"issue_type":"task","owner":"Calmcacil@Raion","created_at":"2026-01-12T16:27:53.148859434+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T16:31:29.339557739+01:00","closed_at":"2026-01-12T16:31:29.339557739+01:00","close_reason":"Config file approach replaced with interactive prompts using 'read', with 'WGI_' prefixed environment variable overrides. No persistent config file needed."} @@ -46,14 +46,14 @@ {"id":"wg-admin-qtb","title":"Replace clipboard copy with config display window for SSH sessions","description":"# Replace Clipboard Copy with Configuration Display\n\n## Problem\n\nThe TUI application currently has a \"Copy Public Key\" feature (press 'c' in detail screen) that doesn't work over SSH sessions. When running the application remotely via SSH:\n\n1. **Clipboard access is not available** - SSH sessions don't provide clipboard functionality\n2. **Feature is non-functional** - Users cannot copy public keys or configuration\n3. **Workaround required** - Users must manually locate and read config files\n4. **Poor UX** - No easy way to get client configuration for use\n\n## Current Implementation\n\nThe detail screen has a `copyPublicKey()` function that:\n- Returns `clipboardCopiedMsg` message\n- Shows \"Public key copied to clipboard!\" feedback\n- Uses no actual clipboard library (not implemented)\n- Is completely non-functional over SSH\n\n## Proposed Solution\n\nReplace the clipboard copy feature with a **Configuration Display Modal** that:\n\n### 1. Shows Full Client Configuration\n- Displays complete WireGuard client configuration file\n- Includes all sections: [Interface] and [Peer]\n- Shows keys, IPs, endpoints, and all configuration options\n- Useful for:\n - Copying configuration to other devices\n - Manual configuration setup\n - Troubleshooting and debugging\n - Sharing with other users\n\n### 2. Scrollable Content\n- Uses Bubble Tea viewport component for scrolling\n- Supports keyboard navigation:\n - ↑/k - Line up\n - ↓/j - Line down\n - pgup/b - Page up\n - pgdown/f - Page down\n - g - Go to top\n - G - Go to bottom\n- Handles long configurations gracefully\n\n### 3. SSH-Friendly\n- Modal displays text directly on screen\n- Users can select and copy text with mouse in terminal\n- Works in any terminal emulator over SSH\n- No clipboard API required\n\n### 4. Easy to Close\n- Esc key closes modal\n- q key closes modal\n- Returns to detail screen\n\n## Implementation Details\n\n### New Component: ConfigDisplayModal\n\nCreated `internal/tui/components/config-display.go` with:\n\n**Features:**\n- Displays full client configuration in styled modal\n- Scrollable viewport for long content\n- Rounded border with title \"📋 Client Configuration\"\n- Help text at bottom with navigation instructions\n- Centered on screen with proper positioning\n\n**Key Bindings:**\n```\n↑/j - Scroll down\n↓/k - Scroll up\npgup/b - Page up\npgdn/f - Page down\ng - Go to top\nG - Go to bottom\nEsc/q - Close modal\n```\n\n### Updated Detail Screen\n\nChanged `internal/tui/screens/detail.go`:\n\n**Removed:**\n- `clipboardCopied` field from DetailScreen struct\n- `clipboardTimer` field from DetailScreen struct\n- `copyPublicKey()` function\n- `clipboardCopiedMsg` type\n- Clipboard timeout handling logic\n- \"✓ Public key copied to clipboard!\" display\n\n**Added:**\n- `configDisplay *components.ConfigDisplayModel` field\n- `showConfig bool` field\n- `loadConfig()` function that:\n - Loads client configuration via `wireguard.GetClientConfigContent()`\n - Creates config display modal\n - Shows modal with loaded content\n- Config display modal handling in Update() method\n- Config display modal rendering in View() method\n\n**Changed:**\n- Key handler for 'c': Now calls `loadConfig()` instead of `copyPublicKey()`\n- Help text: Changed from \"[c] Copy Public Key\" to \"[c] View Config\"\n\n## Benefits\n\n1. **Works over SSH** - No clipboard API needed\n2. **More useful** - Shows full configuration, not just public key\n3. **Better UX** - Scrollable, easy to read and select\n4. **Flexible** - Users can copy any part of the configuration\n5. **No external dependencies** - Pure terminal-based solution\n\n## Use Cases\n\n### Scenario 1: User needs config for manual setup\n1. Open client details\n2. Press 'c' to view config\n3. Select and copy full configuration text\n4. Paste into configuration file on target device\n\n### Scenario 2: Troubleshooting\n1. Open client details\n2. Press 'c' to view config\n3. Compare with working configuration\n4. Identify differences or issues\n\n### Scenario 3: Sharing configuration\n1. Open client details\n2. Press 'c' to view config\n3. Select config content\n4. Copy and share with other users/admins\n\n## Files Created\n\n- `internal/tui/components/config-display.go` - Configuration display modal component\n\n## Files Modified\n\n- `internal/tui/screens/detail.go`\n - Updated DetailScreen struct\n - Replaced copyPublicKey with loadConfig\n - Added config display handling\n - Updated help text\n\n## Technical Notes\n\n- Uses Bubble Tea viewport for scrolling\n- Leverages existing `wireguard.GetClientConfigContent()` function\n- Modal styled with lipgloss (rounded border, consistent colors)\n- Viewport dimensions: 76x24 (content area)\n- Modal dimensions: 80x24 (with borders and padding)\n\n## Testing Considerations\n\n- Test with long configuration files (ensure scrolling works)\n- Test with various terminal sizes (ensure modal centers correctly)\n- Test over SSH session (ensure text is selectable/copyable)\n- Test with mouse-enabled terminals (verify selection works)","status":"closed","priority":1,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T22:30:26.291472155+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T22:36:25.862565024+01:00","closed_at":"2026-01-12T22:36:25.862565024+01:00","close_reason":"Implemented config display modal to replace non-functional clipboard copy. Users can now view full client configuration in a scrollable window that works over SSH sessions. Press 'c' in detail screen to view config."} {"id":"wg-admin-rfo","title":"CRITICAL: Cannot exit client details screen - navigation broken","description":"# CRITICAL BUG - Cannot exit screens\n\n## Problem\n\nUser is unable to navigate back from certain screens in the TUI. When opening client details or restore screens, pressing the back key ('b' or 'esc') does nothing, forcing the user to kill the process from a separate terminal.\n\n## Root Cause\n\nThe screen Update() method returns `(Screen, tea.Cmd)` tuple. When a screen wants to signal \"go back to previous screen\", it should return `(nil, tea.Cmd)` as the first value.\n\nHowever, both `detail.go` and `restore.go` were incorrectly returning `(s, nil)` instead of `(nil, nil)` when the back key was pressed. This caused the main model's navigation logic to never detect the screen change:\n\n```go\n// In main.go line 107:\nif newScreen == nil { // This check fails because newScreen == s, not nil\n // Go back to previous screen\n m.currentScreen = m.previousScreen\n m.previousScreen = nil\n}\n```\n\n## Issues Found\n\n### detail.go (line 116)\n**Before:**\n```go\ncase \"b\", \"esc\":\n // Return to list screen - signal parent to switch screens\n return s, nil // BUG: Returns current screen, not nil\n```\n\n**After:**\n```go\ncase \"b\", \"esc\":\n // Return to list screen - signal parent to switch screens\n return nil, nil // FIXED: Returns nil to signal screen change\n```\n\n### restore.go (line 101)\n**Before:**\n```go\ncase \"q\", \"esc\":\n // Return to list screen - signal parent to switch screens\n return s, nil // BUG: Returns current screen, not nil\n```\n\n**After:**\n```go\ncase \"q\", \"esc\":\n // Return to list screen - signal parent to switch screens\n return nil, nil // FIXED: Returns nil to signal screen change\n```\n\n## Impact\n\n- Users get trapped in detail and restore screens\n- Cannot return to main list screen\n- Must kill process and restart application\n- Critical usability issue\n\n## Fix Applied\n\nChanged both occurrences from `return s, nil` to `return nil, nil` to properly signal the main model to switch back to the previous screen.\n\n## Verification\n\n- Build successful with no errors\n- Navigation logic now correctly detects nil return value\n- Screen switching works as expected\n\n## Files Modified\n\n- `internal/tui/screens/detail.go` - Line 116\n- `internal/tui/screens/restore.go` - Line 101","status":"closed","priority":0,"issue_type":"bug","owner":"Calmcacil@Raion","created_at":"2026-01-12T22:22:51.43957087+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T22:27:03.641284763+01:00","closed_at":"2026-01-12T22:27:03.641284763+01:00","close_reason":"Fixed navigation bug by changing 'return s, nil' to 'return nil, nil' in detail.go and restore.go. Both screens now correctly signal main model to switch back to previous screen when back key is pressed."} {"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-ti0","title":"Investigate q key behavior and parseHandshake bug","description":"Investigate q key behavior causing app to not return properly from client details. parseHandshake fix has been committed separately.","status":"open","priority":1,"issue_type":"bug","owner":"Calmcacil@Raion","created_at":"2026-01-12T19:26:35.435240285+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T19:27:21.412723919+01:00"} +{"id":"wg-admin-ti0","title":"Investigate q key behavior and parseHandshake bug","description":"Investigate q key behavior causing app to not return properly from client details. parseHandshake fix has been committed separately.","status":"in_progress","priority":1,"issue_type":"bug","owner":"Calmcacil@Raion","created_at":"2026-01-12T19:26:35.435240285+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T22:59:01.015097211+01:00"} {"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-v7g","title":"Fix search box functionality on main client list screen","description":"# Fix Search Box Functionality\n\n## Problem\n\nThe search box on the main client list screen is not working properly. Users press '/' to activate search, but the functionality does not work as expected.\n\n## Current Implementation\n\nIn `internal/tui/screens/list.go`:\n\n**Activation (lines 55-59):**\n```go\nif msg.String() == \"/\" \u0026\u0026 !s.search.IsActive() {\n s.search.Activate()\n return s, nil\n}\n```\n\n**Search Active Handling (lines 61-67):**\n```go\nif s.search.IsActive() {\n s.search, cmd = s.search.Update(msg)\n // Apply filter to clients\n s.applyFilter()\n return s, cmd\n}\n```\n\n**View Rendering (line 135):**\n```go\nreturn s.search.View() + \"\\n\" + s.table.View()\n```\n\n## Possible Issues\n\n### 1. Search Not Focusing Input\nThe search component may not be properly setting input focus when activated.\n\n### 2. Table Not Rebuilding After Filter\nThe `applyFilter()` function may not be properly rebuilding the table with filtered results.\n\n### 3. Keyboard Event Handling\nSearch input may not be receiving keyboard events properly when active.\n\n### 4. Search Component Internal Issues\nThe SearchModel component in `internal/tui/components/search.go` may have bugs:\n- Input not updating\n- Filter not applying correctly\n- Active state not being tracked properly\n\n## Investigation Needed\n\n1. **Test search activation**: Verify pressing '/' actually activates search\n2. **Test search input**: Verify typing characters updates search input field\n3. **Test filtering**: Verify filtering actually filters the client list\n4. **Check component**: Review SearchModel implementation for bugs\n5. **Test filter application**: Verify `applyFilter()` correctly rebuilds table\n\n## Expected Behavior\n\n1. User presses '/' to activate search\n2. Search bar appears with focus indicator\n3. User types search query (e.g., \"laptop\")\n4. Table filters to show only matching clients\n5. Matching text is highlighted (if implemented)\n6. User can navigate table results\n7. Press 'Enter' to select filtered result\n8. Press 'Esc' to clear search\n\n## Files to Check\n\n- `internal/tui/screens/list.go` - Main list screen with search\n- `internal/tui/components/search.go` - Search component\n- `internal/tui/screens/detail.go` - Check for comparison (if search works there)\n\n## Dependencies\n\nNone - this is a bug fix\n\n## Priority\n\n**CRITICAL (P0)** - Search is a primary navigation feature and should work reliably","status":"closed","priority":0,"issue_type":"bug","owner":"Calmcacil@Raion","created_at":"2026-01-12T22:38:54.032294512+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T22:54:27.21756834+01:00","closed_at":"2026-01-12T22:54:27.21756834+01:00","close_reason":"Not a bug - search functionality works correctly with '/' key to activate. User confirmed it's resolved."} {"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:26:35.464413104+01:00","closed_at":"2026-01-12T19:26:35.464413104+01:00","close_reason":"Fixed parseHandshake to correctly parse all time units with number-unit pairing"} -{"id":"wg-admin-wmo","title":"Create dedicated error screen with user-friendly messages and recovery options","description":"Errors are displayed but lack recovery guidance. Create error screen component that maps technical errors to user-friendly messages and provides actionable next steps. Examples: 'Permission denied' -\u003e 'Run with sudo', 'Client already exists' -\u003e 'Try different name'.","status":"open","priority":2,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:40:23.214356805+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T21:40:23.214356805+01:00"} +{"id":"wg-admin-wmo","title":"Create dedicated error screen with user-friendly messages and recovery options","description":"Errors are displayed but lack recovery guidance. Create error screen component that maps technical errors to user-friendly messages and provides actionable next steps. Examples: 'Permission denied' -\u003e 'Run with sudo', 'Client already exists' -\u003e 'Try different name'.","status":"in_progress","priority":2,"issue_type":"feature","owner":"Calmcacil@Raion","created_at":"2026-01-12T21:40:23.214356805+01:00","created_by":"Calmcacil","updated_at":"2026-01-12T22:58:57.827395925+01:00"} {"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/internal/tui/components/breadcrumb.go b/internal/tui/components/breadcrumb.go new file mode 100644 index 0000000..35021b8 --- /dev/null +++ b/internal/tui/components/breadcrumb.go @@ -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, "")) +} diff --git a/internal/tui/screens/add.go b/internal/tui/screens/add.go index 83516c6..892e1cf 100644 --- a/internal/tui/screens/add.go +++ b/internal/tui/screens/add.go @@ -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" @@ -13,8 +14,10 @@ import ( // AddScreen is a form for adding new WireGuard clients type AddScreen struct { - form *huh.Form - quitting bool + form *huh.Form + quitting bool + spinner spinner.Model + isCreating bool } // Styles diff --git a/internal/tui/screens/detail.go b/internal/tui/screens/detail.go index 99b4779..91c62c5 100644 --- a/internal/tui/screens/detail.go +++ b/internal/tui/screens/detail.go @@ -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 diff --git a/internal/tui/screens/help.go b/internal/tui/screens/help.go index cd22155..5cdfd5c 100644 --- a/internal/tui/screens/help.go +++ b/internal/tui/screens/help.go @@ -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") diff --git a/internal/tui/screens/list.go b/internal/tui/screens/list.go index ad5a809..5da740f 100644 --- a/internal/tui/screens/list.go +++ b/internal/tui/screens/list.go @@ -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,16 +12,17 @@ import ( "github.com/charmbracelet/lipgloss" ) -const statusRefreshInterval = 10 // seconds +const statusRefreshInterval = 3 // seconds // ListScreen displays a table of WireGuard clients type ListScreen struct { - table table.Model - search *components.SearchModel - clients []ClientWithStatus - filtered []ClientWithStatus - sortedBy string // Column name being sorted by - ascending bool // Sort direction + table table.Model + search *components.SearchModel + clients []ClientWithStatus + 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