package screens import ( "strings" "github.com/calmcacil/wg-admin/internal/tui/theme" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) // ErrorScreen displays user-friendly error messages with recovery options type ErrorScreen struct { err error friendly string actions []ErrorAction quitting bool } // ErrorAction represents a recovery action type ErrorAction struct { Key string Label string Description string } // NewErrorScreen creates a new error screen with mapped error information func NewErrorScreen(err error) *ErrorScreen { screen := &ErrorScreen{ err: err, friendly: err.Error(), // Fallback to raw error actions: []ErrorAction{{Key: "enter", Label: "OK", Description: "Dismiss and return"}}, quitting: false, } // Map error to user-friendly message and recovery options screen.mapError(err) return screen } // Init initializes the error screen func (s *ErrorScreen) Init() tea.Cmd { return nil } // Update handles messages for the error screen func (s *ErrorScreen) Update(msg tea.Msg) (Screen, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case "q", "ctrl+c": // Quit application s.quitting = true return s, tea.Quit case "enter", "esc": // Dismiss error screen return nil, nil } } return s, nil } // View renders the error screen func (s *ErrorScreen) View() string { if s.quitting { return "" } titleStyle := theme.StyleTitle.Copy().MarginBottom(1) errorStyle := theme.StyleError.Copy().Bold(true) msgStyle := theme.StyleSubtitle.Copy().MarginTop(1).MarginBottom(2) actionTitleStyle := theme.StylePrimary.Copy().Bold(true).MarginTop(1) actionStyle := theme.StyleMuted.Copy().MarginLeft(2) // Build the error display content := lipgloss.JoinVertical( lipgloss.Left, titleStyle.Render("Error Occurred"), errorStyle.Render("⚠ "+s.friendly), msgStyle.Render("Technical Details: "+s.err.Error()), ) // Add recovery actions if len(s.actions) > 1 { content += "\n" + actionTitleStyle.Render("Recovery Options:") for _, action := range s.actions { key := theme.StyleHelpKey.Render("[" + action.Key + "]") content += "\n" + actionStyle.Render( lipgloss.JoinHorizontal(lipgloss.Left, key+" ", action.Label+" - "+action.Description, ), ) } } content += "\n\n" + theme.StyleMuted.Render("Press Enter to dismiss • Press q to quit") return lipgloss.NewStyle(). Padding(1, 2). Border(lipgloss.RoundedBorder()). BorderForeground(theme.GetCurrentTheme().Scheme.Error). Render(content) } // mapError converts technical errors to user-friendly messages and recovery options func (s *ErrorScreen) mapError(err error) { if err == nil { return } errStr := strings.ToLower(err.Error()) // Permission errors if strings.Contains(errStr, "permission") || strings.Contains(errStr, "denied") || strings.Contains(errStr, "operation not permitted") { s.friendly = "Permission Denied" s.actions = []ErrorAction{ {Key: "r", Label: "Run with sudo", Description: "Restart with elevated privileges"}, {Key: "enter", Label: "Dismiss", Description: "Return to previous screen"}, } return } // File not found errors if strings.Contains(errStr, "no such file") || strings.Contains(errStr, "file not found") || strings.Contains(errStr, "does not exist") { s.friendly = "Configuration File Missing" s.actions = []ErrorAction{ {Key: "r", Label: "Restore", Description: "Restore from backup (if available)"}, {Key: "enter", Label: "Dismiss", Description: "Return to previous screen"}, } return } // Client already exists errors if strings.Contains(errStr, "already exists") || strings.Contains(errStr, "duplicate") { s.friendly = "Client Already Exists" s.actions = []ErrorAction{ {Key: "n", Label: "New Name", Description: "Try a different client name"}, {Key: "enter", Label: "Dismiss", Description: "Return to previous screen"}, } return } // IP address exhaustion if strings.Contains(errStr, "no available") || strings.Contains(errStr, "exhausted") || strings.Contains(errStr, "out of") { s.friendly = "No Available IP Addresses" s.actions = []ErrorAction{ {Key: "d", Label: "Delete Client", Description: "Remove an unused client to free IPs"}, {Key: "enter", Label: "Dismiss", Description: "Return to previous screen"}, } return } // Config file parsing errors if strings.Contains(errStr, "parse") || strings.Contains(errStr, "invalid") || strings.Contains(errStr, "malformed") { s.friendly = "Invalid Configuration" s.actions = []ErrorAction{ {Key: "r", Label: "Restore", Description: "Restore from backup"}, {Key: "m", Label: "Manual Fix", Description: "Edit configuration manually"}, {Key: "enter", Label: "Dismiss", Description: "Return to previous screen"}, } return } // WireGuard command failures if strings.Contains(errStr, "wg genkey") || strings.Contains(errStr, "wg pubkey") || strings.Contains(errStr, "wg genpsk") || strings.Contains(errStr, "wg set") { s.friendly = "WireGuard Command Failed" s.actions = []ErrorAction{ {Key: "i", Label: "Install WG", Description: "Ensure WireGuard is installed"}, {Key: "s", Label: "Check Service", Description: "Verify WireGuard service is running"}, {Key: "enter", Label: "Dismiss", Description: "Return to previous screen"}, } return } // Network errors if strings.Contains(errStr, "network") || strings.Contains(errStr, "connection") || strings.Contains(errStr, "timeout") { s.friendly = "Network Error" s.actions = []ErrorAction{ {Key: "r", Label: "Retry", Description: "Attempt the operation again"}, {Key: "c", Label: "Check Connection", Description: "Verify network connectivity"}, {Key: "enter", Label: "Dismiss", Description: "Return to previous screen"}, } return } // Config directory not found if strings.Contains(errStr, "config directory") || strings.Contains(errStr, "wireguard") { s.friendly = "WireGuard Not Configured" s.actions = []ErrorAction{ {Key: "i", Label: "Install WireGuard", Description: "Set up WireGuard on this server"}, {Key: "enter", Label: "Dismiss", Description: "Return to previous screen"}, } return } // DNS validation errors if strings.Contains(errStr, "dns") || strings.Contains(errStr, "invalid address") { s.friendly = "Invalid DNS Configuration" s.actions = []ErrorAction{ {Key: "e", Label: "Edit DNS", Description: "Update DNS server settings"}, {Key: "d", Label: "Use Default", Description: "Use default DNS (8.8.8.8, 8.8.4.4)"}, {Key: "enter", Label: "Dismiss", Description: "Return to previous screen"}, } return } // Backup/restore errors if strings.Contains(errStr, "backup") || strings.Contains(errStr, "restore") { s.friendly = "Backup Operation Failed" s.actions = []ErrorAction{ {Key: "r", Label: "Retry", Description: "Attempt backup/restore again"}, {Key: "c", Label: "Check Space", Description: "Verify disk space is available"}, {Key: "enter", Label: "Dismiss", Description: "Return to previous screen"}, } return } // Default: generic error s.friendly = "An Error Occurred" s.actions = []ErrorAction{ {Key: "r", Label: "Retry", Description: "Attempt the operation again"}, {Key: "enter", Label: "Dismiss", Description: "Return to previous screen"}, } }