package main import ( "fmt" "os" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/calmcacil/wg-admin/internal/config" "github.com/calmcacil/wg-admin/internal/tui/screens" ) const version = "0.1.0" var ( styleTitle = lipgloss.NewStyle().Foreground(lipgloss.Color("62")).Bold(true) styleSubtitle = lipgloss.NewStyle().Foreground(lipgloss.Color("241")) styleSuccess = lipgloss.NewStyle().Foreground(lipgloss.Color("46")) styleError = lipgloss.NewStyle().Foreground(lipgloss.Color("196")) styleHelpKey = lipgloss.NewStyle().Foreground(lipgloss.Color("63")).Bold(true) ) type model struct { currentScreen screens.Screen previousScreen screens.Screen quitting bool initialized bool errorScreen *screens.ErrorScreen } func (m model) Init() tea.Cmd { if os.Geteuid() != 0 { fmt.Println(styleError.Render("ERROR: Must run as root")) os.Exit(1) } _, _ = config.LoadConfig() return nil } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Handle initialization on first update after Init if !m.initialized { m.initialized = true m.currentScreen = screens.NewListScreen() return m, m.currentScreen.Init() } switch msg := msg.(type) { case tea.KeyMsg: // Only quit on 'q' or ctrl+c when on list screen // Other screens handle their own navigation keys if msg.String() == "q" || msg.String() == "ctrl+c" { if _, ok := m.currentScreen.(*screens.ListScreen); ok { m.quitting = true return m, tea.Quit } // For other screens, let them handle the key } switch msg.String() { case "?": // Switch to help screen m.previousScreen = m.currentScreen m.currentScreen = screens.NewHelpScreen(m.previousScreen) return m, m.currentScreen.Init() case "l": // Switch to list screen m.currentScreen = screens.NewListScreen() return m, m.currentScreen.Init() case "a": // Switch to add screen m.previousScreen = m.currentScreen m.currentScreen = screens.NewAddScreen() return m, m.currentScreen.Init() } case screens.ClientSelectedMsg: // User selected a client - show detail screen m.previousScreen = m.currentScreen m.currentScreen = screens.NewDetailScreen(msg.Client.Client) return m, m.currentScreen.Init() case screens.ClientDeletedMsg: // Client was deleted - show success message and return to list m.currentScreen = screens.NewListScreen() return m, m.currentScreen.Init() case screens.ClientCreatedMsg: // Client was created - return to list screen m.currentScreen = screens.NewListScreen() return m, m.currentScreen.Init() case screens.RestoreCompletedMsg: // Restore completed - return to list screen to refresh clients m.currentScreen = screens.NewListScreen() return m, m.currentScreen.Init() case screens.CloseDetailScreenMsg: // Detail screen closed - go back to previous screen if m.previousScreen != nil { m.currentScreen = m.previousScreen m.previousScreen = nil return m, m.currentScreen.Init() } m.currentScreen = screens.NewListScreen() return m, m.currentScreen.Init() } // Pass messages to current screen if m.currentScreen != nil { newScreen, cmd := m.currentScreen.Update(msg) // If screen returns nil, go back to previous screen if newScreen == nil { if m.previousScreen != nil { m.currentScreen = m.previousScreen m.previousScreen = nil } } else if newScreen != m.currentScreen { // Screen is switching to a different screen m.previousScreen = m.currentScreen m.currentScreen = newScreen } return m, cmd } return m, nil } func (m model) View() string { if m.quitting { return "\nGoodbye!\n" } if m.currentScreen != nil && m.initialized { return m.currentScreen.View() } title := styleTitle.Render("WireGuard Client Manager") subtitle := styleSubtitle.Render(fmt.Sprintf("v%s", version)) bar := fmt.Sprintf("Press %s for help, %s to quit", styleHelpKey.Render("?"), styleHelpKey.Render("q")) return title + " " + subtitle + "\n\nInitializing...\n\n" + bar } func main() { p := tea.NewProgram(model{}, tea.WithAltScreen()) if _, err := p.Run(); err != nil { fmt.Printf("Error: %v\n", err) os.Exit(1) } }