Add WireGuard TUI implementation

- Add Go TUI with bubbletea for WireGuard management
- Implement client CRUD operations with QR code generation
- Add configuration and validation modules
- Install/update scripts for client setup
- Update Makefile to build binaries to bin/ directory
- Add .gitignore for Go projects
This commit is contained in:
Calmcacil
2026-01-12 19:03:35 +01:00
parent 5ac68db854
commit 26120b8bc2
37 changed files with 6330 additions and 97 deletions

View File

@@ -0,0 +1,143 @@
package components
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
// ConfirmModel represents a confirmation modal
type ConfirmModel struct {
Message string
Yes bool // true = yes, false = no
Visible bool
Width int
Height int
}
// Styles
var (
confirmModalStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("196")).
Padding(1, 2).
Background(lipgloss.Color("235"))
confirmTitleStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("226")).
Bold(true)
confirmMessageStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("255")).
Width(50)
confirmHelpStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("241")).
MarginTop(1)
)
// NewConfirm creates a new confirmation modal
func NewConfirm(message string, width, height int) *ConfirmModel {
return &ConfirmModel{
Message: message,
Yes: false,
Visible: true,
Width: width,
Height: height,
}
}
// Init initializes the confirmation modal
func (m *ConfirmModel) Init() tea.Cmd {
return nil
}
// Update handles messages for the confirmation modal
func (m *ConfirmModel) Update(msg tea.Msg) (*ConfirmModel, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "y", "Y", "left":
m.Yes = true
case "n", "N", "right":
m.Yes = false
case "enter":
// Confirmed - will be handled by parent
return m, nil
case "esc":
m.Visible = false
return m, nil
}
}
return m, nil
}
// View renders the confirmation modal
func (m *ConfirmModel) View() string {
if !m.Visible {
return ""
}
// Build modal content
content := lipgloss.JoinVertical(
lipgloss.Left,
confirmTitleStyle.Render("⚠️ Confirm Action"),
"",
confirmMessageStyle.Render(m.Message),
"",
m.renderOptions(),
)
// Apply modal style
modal := confirmModalStyle.Render(content)
// Center modal on screen
modalWidth := lipgloss.Width(modal)
modalHeight := lipgloss.Height(modal)
x := (m.Width - modalWidth) / 2
if x < 0 {
x = 0
}
y := (m.Height - modalHeight) / 2
if y < 0 {
y = 0
}
return lipgloss.Place(m.Width, m.Height,
lipgloss.Left, lipgloss.Top,
modal,
lipgloss.WithWhitespaceChars(" "),
lipgloss.WithWhitespaceForeground(lipgloss.Color("235")),
)
}
// renderOptions renders the yes/no options
func (m *ConfirmModel) renderOptions() string {
yesStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
noStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
selectedStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("57")).
Bold(true).
Underline(true)
var yesText, noText string
if m.Yes {
yesText = selectedStyle.Render("[Yes]")
noText = noStyle.Render(" No ")
} else {
yesText = yesStyle.Render(" Yes ")
noText = selectedStyle.Render("[No]")
}
helpText := confirmHelpStyle.Render("←/→ to choose • Enter to confirm • Esc to cancel")
return lipgloss.JoinHorizontal(lipgloss.Left, yesText, " ", noText, "\n", helpText)
}
// IsConfirmed returns true if user confirmed with Yes
func (m *ConfirmModel) IsConfirmed() bool {
return m.Yes
}
// IsCancelled returns true if user cancelled
func (m *ConfirmModel) IsCancelled() bool {
return !m.Visible
}