diff --git a/main.go b/main.go index 5aa0ce9..745a74b 100644 --- a/main.go +++ b/main.go @@ -2,20 +2,20 @@ package main import ( "fmt" + "net" "os" "os/exec" "strconv" "strings" - "net" "time" "pulsegate/internal/config" "pulsegate/internal/models" "pulsegate/internal/secret" + "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/bubbles/textinput" "golang.org/x/term" ) @@ -28,7 +28,7 @@ const ( ViewAddServer viewMode = "add_server" ViewEditServer viewMode = "edit_server" ViewDeleteConfirm viewMode = "delete_confirm" - ViewCommands viewMode = "commands" + ViewCommands viewMode = "commands" ViewCommandOutput viewMode = "command_output" ) @@ -41,25 +41,28 @@ type model struct { height int err error - addInputs []textinput.Model - addFocus int - editIndex int - commandSelected int - commandOutput string - commandTitle string - commandError string - serverStatus map[int]string + addInputs []textinput.Model + addFocus int + editIndex int + commandSelected int + commandOutput string + commandTitle string + commandError string + serverStatus map[int]string + settingsSelected int + settingsEditingTerm bool + settingsTermInput textinput.Model } var ( - green = lipgloss.Color("#00ff99") - cyan = lipgloss.Color("#33ccff") - gray = lipgloss.Color("#777777") - text = lipgloss.Color("#d7ffe9") - dimText = lipgloss.Color("#8aa99b") - panelBg = lipgloss.Color("#07110d") - border = lipgloss.Color("#00aa66") - warn = lipgloss.Color("#ffaa00") + green = lipgloss.Color("#00ff99") + cyan = lipgloss.Color("#33ccff") + gray = lipgloss.Color("#777777") + text = lipgloss.Color("#d7ffe9") + dimText = lipgloss.Color("#8aa99b") + panelBg = lipgloss.Color("#07110d") + border = lipgloss.Color("#00aa66") + warn = lipgloss.Color("#ffaa00") baseStyle = lipgloss.NewStyle(). Foreground(text) @@ -109,14 +112,15 @@ func initialModel() model { } m := model{ - cfg: cfg, - servers: cfg.Servers, - selected: 0, - view: ViewServers, + cfg: cfg, + servers: cfg.Servers, + selected: 0, + view: ViewServers, serverStatus: make(map[int]string), } m.initAddInputs() + m.initSettingsInput() return m } @@ -161,6 +165,16 @@ func (m *model) initAddInputs() { } } +func (m *model) initSettingsInput() { + input := textinput.New() + input.Placeholder = "TERM Override" + input.CharLimit = 80 + input.Width = 32 + input.SetValue(m.cfg.Settings.Terminal.Term) + + m.settingsTermInput = input +} + func (m *model) saveNewServer() error { port, err := strconv.Atoi(m.addInputs[3].Value()) if err != nil { @@ -240,7 +254,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } m.serverStatus[msg.Index] = msg.Status - return m, nil + return m, nil case tea.KeyMsg: @@ -271,7 +285,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.addFocus = (m.addFocus + 1) % len(m.addInputs) m.addInputs[m.addFocus].Focus() return m, nil - + case "r": return m, checkAllServerStatus(m.servers) @@ -326,6 +340,74 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } + if m.view == ViewSettings { + if m.settingsEditingTerm { + switch msg.String() { + case "ctrl+c": + return m, tea.Quit + + case "esc": + m.settingsEditingTerm = false + m.settingsTermInput.Blur() + m.settingsTermInput.SetValue(m.cfg.Settings.Terminal.Term) + return m, nil + + case "enter": + m.cfg.Settings.Terminal.Term = strings.TrimSpace(m.settingsTermInput.Value()) + if err := m.saveSettings(); err != nil { + m.err = err + } + m.settingsEditingTerm = false + m.settingsTermInput.Blur() + return m, nil + } + + var cmd tea.Cmd + m.settingsTermInput, cmd = m.settingsTermInput.Update(msg) + return m, cmd + } + + switch msg.String() { + case "ctrl+c": + return m, tea.Quit + + case "q", "esc": + m.view = ViewServers + return m, nil + + case "up", "k": + if m.settingsSelected > 0 { + m.settingsSelected-- + } + return m, nil + + case "down", "j": + if m.settingsSelected < settingsOptionCount()-1 { + m.settingsSelected++ + } + return m, nil + + case "enter", " ": + switch m.settingsSelected { + case 0: + m.cfg.Settings.Terminal.EnableKittyFix = !m.cfg.Settings.Terminal.EnableKittyFix + if err := m.saveSettings(); err != nil { + m.err = err + } + case 1: + m.settingsEditingTerm = true + m.settingsTermInput.SetValue(m.cfg.Settings.Terminal.Term) + m.settingsTermInput.Focus() + case 2: + m.cfg.Settings.Theme = nextTheme(m.cfg.Settings.Theme) + if err := m.saveSettings(); err != nil { + m.err = err + } + } + return m, nil + } + } + // Normale Navigation switch msg.String() { case "ctrl+c": @@ -370,6 +452,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "s": m.view = ViewSettings + m.settingsEditingTerm = false + m.settingsTermInput.Blur() return m, nil case "h": @@ -451,7 +535,7 @@ func (m model) View() string { case ViewCommands: right = m.renderCommandsContent() case ViewCommandOutput: - right = m.renderCommandOutputContent() + right = m.renderCommandOutputContent() default: right = m.renderServerList() } @@ -586,19 +670,57 @@ func (m model) renderServerList() string { } func (m model) renderSettingsContent() string { + kittyValue := "aus" + if m.cfg.Settings.Terminal.EnableKittyFix { + kittyValue = "an" + } + + termValue := m.cfg.Settings.Terminal.Term + if termValue == "" { + termValue = "xterm-256color" + } + + themeValue := m.cfg.Settings.Theme + if themeValue == "" { + themeValue = "neon-green" + } + + options := []string{ + fmt.Sprintf("Kitty Fix global: %s", kittyValue), + fmt.Sprintf("TERM Override: %s", termValue), + fmt.Sprintf("Theme: %s", themeValue), + } + lines := []string{ badgeStyle.Render(" SETTINGS "), "", selectedStyle.Render("Terminal"), - fmt.Sprintf(" Kitty Fix global: %v", m.cfg.Settings.Terminal.EnableKittyFix), - fmt.Sprintf(" TERM Override: %s", m.cfg.Settings.Terminal.Term), + } + + for i, option := range options { + if i == 1 && m.settingsEditingTerm { + option = "TERM Override: " + m.settingsTermInput.View() + } + + if i == m.settingsSelected { + lines = append(lines, selectedStyle.Render("> "+option)) + } else { + lines = append(lines, normalStyle.Render(" "+option)) + } + } + + lines = append(lines, "", selectedStyle.Render("Hinweis"), - " Wenn Kitty Fix aktiv ist, startet SSH mit TERM=xterm-256color.", + " Wenn Kitty Fix aktiv ist, startet SSH mit dem gewählten TERM-Wert.", " Das verhindert auf Ubuntu/Debian oft:", " 'Error opening terminal: xterm-kitty'", "", - mutedStyle.Render("Später bauen wir hier Toggle-Optionen mit Enter ein."), + mutedStyle.Render("↑/↓ Auswahl Enter/Space ändern Esc/q zurück"), + ) + + if m.settingsEditingTerm { + lines = append(lines, mutedStyle.Render("TERM bearbeiten: Enter speichern Esc abbrechen")) } return panelStyle. @@ -607,6 +729,35 @@ func (m model) renderSettingsContent() string { Render(strings.Join(lines, "\n")) } +func settingsOptionCount() int { + return 3 +} + +func nextTheme(current string) string { + themes := []string{"neon-green", "cyan", "plain"} + + for i, theme := range themes { + if current == theme { + return themes[(i+1)%len(themes)] + } + } + + return themes[0] +} + +func (m *model) saveSettings() error { + if m.cfg.Settings.Terminal.Term == "" { + m.cfg.Settings.Terminal.Term = "xterm-256color" + m.settingsTermInput.SetValue(m.cfg.Settings.Terminal.Term) + } + + if m.cfg.Settings.Theme == "" { + m.cfg.Settings.Theme = "neon-green" + } + + return config.SaveConfig(getConfigPath(), m.cfg) +} + func (m model) renderHelpContent() string { lines := []string{ badgeStyle.Render(" HELP "), @@ -731,7 +882,6 @@ func max(a, b int) int { return b } - func (m *model) loadServerIntoForm(server models.Server) { m.initAddInputs() @@ -822,7 +972,6 @@ func (m *model) deleteSelectedServer() error { return nil } - func (m model) renderEditServerContent() string { return m.renderServerFormContent(" EDIT SERVER ", "Server bearbeiten") } @@ -1154,4 +1303,4 @@ func main() { fmt.Println("Fehler:", err) os.Exit(1) } -} \ No newline at end of file +}