package main import ( "fmt" "io" "os" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/huh" "periodic/prddeploy/commands" ) type sshExecCmd struct { run func() } func (c *sshExecCmd) Run() error { c.run(); return nil } func (c *sshExecCmd) SetStdin(_ io.Reader) {} func (c *sshExecCmd) SetStdout(_ io.Writer) {} func (c *sshExecCmd) SetStderr(_ io.Writer) {} type appState int const ( stateList appState = iota stateForm ) type CommandItem struct { ItemTitle string `json:"title"` ItemDescription string `json:"description"` } func (i CommandItem) FilterValue() string { return i.ItemTitle } func (i CommandItem) Title() string { return i.ItemTitle } func (i CommandItem) Description() string { return i.ItemDescription } func (i CommandItem) String() string { return i.ItemTitle } type model struct { list list.Model form *huh.Form state appState choice int RemoteCommand string PrivateKeyFile string Username string Host string LocalFilepath string DestinationFilepath string } func (m model) DebugState() { fmt.Printf("REMOTE COMMAND: %s\n", m.RemoteCommand) fmt.Printf("PRIVATE KEY FILE: %s\n", m.PrivateKeyFile) fmt.Printf("USERNAME: %s\n", m.Username) fmt.Printf("HOST: %s\n", m.Host) fmt.Printf("LOCAL FILEPATH: %s\n", m.LocalFilepath) fmt.Printf("DESTINATION FILEPATH: %s\n", m.DestinationFilepath) } func (m *model) ReadForm() { m.RemoteCommand = m.form.GetString("remote_command") m.PrivateKeyFile = m.form.GetString("private_key_file") m.Username = m.form.GetString("username") m.Host = m.form.GetString("host") m.LocalFilepath = m.form.GetString("local_filepath") m.DestinationFilepath = m.form.GetString("destination_filepath") } func newModel() model { items := []list.Item{ CommandItem{ItemTitle: "Run Command", ItemDescription: "Run a command on the remote host",}, CommandItem{ItemTitle: "Upload File", ItemDescription: "Upload a file to the remote host",}, } l := list.New(items, list.NewDefaultDelegate(), 80, 20) l.Title = "Commands" return model{list: l, state: stateList} } func (m model) Init() tea.Cmd { return nil } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch m.state { case stateList: switch msg := msg.(type) { case tea.WindowSizeMsg: m.list.SetSize(msg.Width, msg.Height) case tea.KeyMsg: if msg.String() == "enter" { m.choice = m.list.Index() m.state = stateForm if m.choice == 0 { m.form = huh.NewForm( huh.NewGroup( huh.NewInput().Title("Remote Command").Key("remote_command").Value(&m.RemoteCommand), huh.NewInput().Title("Private Key File").Key("private_key_file").Value(&m.PrivateKeyFile), huh.NewInput().Title("Username").Key("username").Value(&m.Username), huh.NewInput().Title("Host").Key("host").Value(&m.Host), ), ) } else { m.form = huh.NewForm( huh.NewGroup( huh.NewInput().Title("Local Filepath").Key("local_filepath").Value(&m.LocalFilepath), huh.NewInput().Title("Destination Filepath").Key("destination_filepath").Value(&m.DestinationFilepath), huh.NewInput().Title("Private Key File").Key("private_key_file").Value(&m.PrivateKeyFile), huh.NewInput().Title("Username").Key("username").Value(&m.Username), huh.NewInput().Title("Host").Key("host").Value(&m.Host), ), ) } return m, m.form.Init() } } var cmd tea.Cmd m.list, cmd = m.list.Update(msg) return m, cmd case stateForm: form, cmd := m.form.Update(msg) if f, ok := form.(*huh.Form); ok { m.form = f } if m.form.State == huh.StateCompleted { m.ReadForm() var execCmd tea.ExecCommand if m.choice == 0 { args := commands.RunCommandArgs{ RemoteCommand: m.RemoteCommand, PrivateKeyFile: m.PrivateKeyFile, Username: m.Username, Host: m.Host, } execCmd = &sshExecCmd{run: func() { commands.RunCommand(args) }} } else { args := commands.UploadFileArgs{ LocalFilepath: m.LocalFilepath, DestinationFilepath: m.DestinationFilepath, PrivateKeyFile: m.PrivateKeyFile, Username: m.Username, Host: m.Host, } execCmd = &sshExecCmd{run: func() { commands.UploadFile(args) }} } m.state = stateList m.form = nil return m, tea.Exec(execCmd, func(err error) tea.Msg { return nil }) } if m.form.State == huh.StateAborted { m.state = stateList m.form = nil return m, nil } return m, cmd } return m, nil } func (m model) View() string { switch m.state { case stateList: return m.list.View() case stateForm: return m.form.View() } return "" } func main() { p := tea.NewProgram(newModel(), tea.WithAltScreen()) if _, err := p.Run(); err != nil { fmt.Fprintf(os.Stderr, "Error running program: %v\n", err) os.Exit(1) } }