181 lines
4.1 KiB
Go
181 lines
4.1 KiB
Go
|
|
package tui
|
||
|
|
|
||
|
|
import (
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"github.com/charmbracelet/bubbles/textinput"
|
||
|
|
tea "github.com/charmbracelet/bubbletea"
|
||
|
|
"github.com/charmbracelet/lipgloss"
|
||
|
|
)
|
||
|
|
|
||
|
|
// SSHConfigModel handles SSH connection input
|
||
|
|
type SSHConfigModel struct {
|
||
|
|
inputs []textinput.Model
|
||
|
|
focusIndex int
|
||
|
|
submitted bool
|
||
|
|
cancelled bool
|
||
|
|
|
||
|
|
// Parsed values
|
||
|
|
Host string
|
||
|
|
Port string
|
||
|
|
User string
|
||
|
|
Password string
|
||
|
|
}
|
||
|
|
|
||
|
|
const (
|
||
|
|
inputHost = iota
|
||
|
|
inputPort
|
||
|
|
inputUser
|
||
|
|
inputPassword
|
||
|
|
)
|
||
|
|
|
||
|
|
// NewSSHConfigModel creates a new SSH config input model
|
||
|
|
func NewSSHConfigModel() SSHConfigModel {
|
||
|
|
inputs := make([]textinput.Model, 4)
|
||
|
|
|
||
|
|
// Host input
|
||
|
|
inputs[inputHost] = textinput.New()
|
||
|
|
inputs[inputHost].Placeholder = "192.168.1.100"
|
||
|
|
inputs[inputHost].Focus()
|
||
|
|
inputs[inputHost].CharLimit = 256
|
||
|
|
inputs[inputHost].Width = 30
|
||
|
|
inputs[inputHost].Prompt = "Host: "
|
||
|
|
|
||
|
|
// Port input
|
||
|
|
inputs[inputPort] = textinput.New()
|
||
|
|
inputs[inputPort].Placeholder = "22"
|
||
|
|
inputs[inputPort].CharLimit = 5
|
||
|
|
inputs[inputPort].Width = 10
|
||
|
|
inputs[inputPort].Prompt = "Port: "
|
||
|
|
inputs[inputPort].SetValue("22")
|
||
|
|
|
||
|
|
// User input
|
||
|
|
inputs[inputUser] = textinput.New()
|
||
|
|
inputs[inputUser].Placeholder = "root"
|
||
|
|
inputs[inputUser].CharLimit = 64
|
||
|
|
inputs[inputUser].Width = 20
|
||
|
|
inputs[inputUser].Prompt = "User: "
|
||
|
|
|
||
|
|
// Password input
|
||
|
|
inputs[inputPassword] = textinput.New()
|
||
|
|
inputs[inputPassword].Placeholder = "password"
|
||
|
|
inputs[inputPassword].CharLimit = 128
|
||
|
|
inputs[inputPassword].Width = 30
|
||
|
|
inputs[inputPassword].Prompt = "Password: "
|
||
|
|
inputs[inputPassword].EchoMode = textinput.EchoPassword
|
||
|
|
inputs[inputPassword].EchoCharacter = '•'
|
||
|
|
|
||
|
|
return SSHConfigModel{
|
||
|
|
inputs: inputs,
|
||
|
|
focusIndex: 0,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Init initializes the model
|
||
|
|
func (m SSHConfigModel) Init() tea.Cmd {
|
||
|
|
return textinput.Blink
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update handles messages
|
||
|
|
func (m SSHConfigModel) Update(msg tea.Msg) (SSHConfigModel, tea.Cmd) {
|
||
|
|
switch msg := msg.(type) {
|
||
|
|
case tea.KeyMsg:
|
||
|
|
switch msg.String() {
|
||
|
|
case "ctrl+c", "esc":
|
||
|
|
m.cancelled = true
|
||
|
|
return m, nil
|
||
|
|
|
||
|
|
case "tab", "down":
|
||
|
|
m.focusIndex++
|
||
|
|
if m.focusIndex >= len(m.inputs) {
|
||
|
|
m.focusIndex = 0
|
||
|
|
}
|
||
|
|
return m, m.updateFocus()
|
||
|
|
|
||
|
|
case "shift+tab", "up":
|
||
|
|
m.focusIndex--
|
||
|
|
if m.focusIndex < 0 {
|
||
|
|
m.focusIndex = len(m.inputs) - 1
|
||
|
|
}
|
||
|
|
return m, m.updateFocus()
|
||
|
|
|
||
|
|
case "enter":
|
||
|
|
if m.focusIndex == len(m.inputs)-1 {
|
||
|
|
// Submit on last field
|
||
|
|
m.submitted = true
|
||
|
|
m.Host = m.inputs[inputHost].Value()
|
||
|
|
m.Port = m.inputs[inputPort].Value()
|
||
|
|
m.User = m.inputs[inputUser].Value()
|
||
|
|
m.Password = m.inputs[inputPassword].Value()
|
||
|
|
return m, nil
|
||
|
|
}
|
||
|
|
// Move to next field
|
||
|
|
m.focusIndex++
|
||
|
|
return m, m.updateFocus()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update focused input
|
||
|
|
cmd := m.updateInputs(msg)
|
||
|
|
return m, cmd
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *SSHConfigModel) updateFocus() tea.Cmd {
|
||
|
|
cmds := make([]tea.Cmd, len(m.inputs))
|
||
|
|
for i := range m.inputs {
|
||
|
|
if i == m.focusIndex {
|
||
|
|
cmds[i] = m.inputs[i].Focus()
|
||
|
|
} else {
|
||
|
|
m.inputs[i].Blur()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return tea.Batch(cmds...)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *SSHConfigModel) updateInputs(msg tea.Msg) tea.Cmd {
|
||
|
|
cmds := make([]tea.Cmd, len(m.inputs))
|
||
|
|
for i := range m.inputs {
|
||
|
|
m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
|
||
|
|
}
|
||
|
|
return tea.Batch(cmds...)
|
||
|
|
}
|
||
|
|
|
||
|
|
// View renders the SSH config form
|
||
|
|
func (m SSHConfigModel) View() string {
|
||
|
|
var b strings.Builder
|
||
|
|
|
||
|
|
titleStyle := lipgloss.NewStyle().
|
||
|
|
Bold(true).
|
||
|
|
Foreground(lipgloss.Color("#7D56F4")).
|
||
|
|
MarginBottom(1)
|
||
|
|
|
||
|
|
b.WriteString(titleStyle.Render("🔌 SSH Connection"))
|
||
|
|
b.WriteString("\n\n")
|
||
|
|
|
||
|
|
for i := range m.inputs {
|
||
|
|
b.WriteString(m.inputs[i].View())
|
||
|
|
b.WriteString("\n")
|
||
|
|
}
|
||
|
|
|
||
|
|
helpStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#626262"))
|
||
|
|
b.WriteString("\n")
|
||
|
|
b.WriteString(helpStyle.Render("Tab/↑↓ navigate • Enter submit • Esc cancel"))
|
||
|
|
|
||
|
|
return b.String()
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsSubmitted returns true if the form was submitted
|
||
|
|
func (m SSHConfigModel) IsSubmitted() bool {
|
||
|
|
return m.submitted
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsCancelled returns true if the form was cancelled
|
||
|
|
func (m SSHConfigModel) IsCancelled() bool {
|
||
|
|
return m.cancelled
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetConfig returns the SSH config values
|
||
|
|
func (m SSHConfigModel) GetConfig() (host, port, user, password string) {
|
||
|
|
return m.Host, m.Port, m.User, m.Password
|
||
|
|
}
|