You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
4.8 KiB
Go
199 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/charmbracelet/bubbles/list"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/huh"
|
|
"periodic/prddeploy/commands"
|
|
)
|
|
|
|
type appState int
|
|
|
|
const (
|
|
stateList appState = iota
|
|
stateForm
|
|
stateOutput
|
|
)
|
|
|
|
type cmdResultMsg struct {
|
|
output string
|
|
}
|
|
|
|
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
|
|
output string
|
|
|
|
RemoteCommand string
|
|
PrivateKeyFile string
|
|
Username string
|
|
Host string
|
|
LocalFilepath string
|
|
DestinationFilepath string
|
|
|
|
CloudflareAPIToken string
|
|
ZoneID string
|
|
RecordName string
|
|
RecordValue 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")
|
|
m.CloudflareAPIToken = m.form.GetString("cloudflare_api_token")
|
|
m.ZoneID = m.form.GetString("zone_id")
|
|
m.RecordName = m.form.GetString("record_name")
|
|
m.RecordValue = m.form.GetString("record_value")
|
|
}
|
|
|
|
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"},
|
|
CommandItem{ItemTitle: "SSL DNS Challenge", ItemDescription: "Set an _acme-challenge TXT record"},
|
|
}
|
|
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 execCommand(choice int, m model) tea.Cmd {
|
|
return func() tea.Msg {
|
|
var buf bytes.Buffer
|
|
var err error
|
|
if choice == 0 {
|
|
err = commands.RunCommand(commands.RunCommandArgs{
|
|
RemoteCommand: m.RemoteCommand,
|
|
PrivateKeyFile: m.PrivateKeyFile,
|
|
Username: m.Username,
|
|
Host: m.Host,
|
|
}, &buf, &buf)
|
|
} else if choice == 1 {
|
|
err = commands.UploadFile(commands.UploadFileArgs{
|
|
LocalFilepath: m.LocalFilepath,
|
|
DestinationFilepath: m.DestinationFilepath,
|
|
PrivateKeyFile: m.PrivateKeyFile,
|
|
Username: m.Username,
|
|
Host: m.Host,
|
|
}, &buf, &buf)
|
|
} else {
|
|
err = commands.SetAcmeRecord(commands.SetAcmeRecordArgs{
|
|
APIToken: m.CloudflareAPIToken,
|
|
ZoneID: m.ZoneID,
|
|
RecordName: m.RecordName,
|
|
RecordValue: m.RecordValue,
|
|
}, &buf, &buf)
|
|
}
|
|
if err != nil {
|
|
fmt.Fprintf(&buf, "\nError: %v", err)
|
|
}
|
|
return cmdResultMsg{output: buf.String()}
|
|
}
|
|
}
|
|
|
|
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
|
|
m.SetForm()
|
|
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()
|
|
choice := m.choice
|
|
m.form = nil
|
|
m.state = stateOutput
|
|
return m, execCommand(choice, m)
|
|
}
|
|
if m.form.State == huh.StateAborted {
|
|
m.state = stateList
|
|
m.form = nil
|
|
return m, nil
|
|
}
|
|
return m, cmd
|
|
|
|
case stateOutput:
|
|
if _, ok := msg.(tea.KeyMsg); ok {
|
|
m.state = stateList
|
|
return m, nil
|
|
}
|
|
}
|
|
|
|
switch msg := msg.(type) {
|
|
case cmdResultMsg:
|
|
m.output = msg.output
|
|
m.state = stateOutput
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func (m model) View() string {
|
|
switch m.state {
|
|
case stateList:
|
|
return m.list.View()
|
|
case stateForm:
|
|
return m.form.View()
|
|
case stateOutput:
|
|
return m.output + "\n\nPress any key to return..."
|
|
}
|
|
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)
|
|
}
|
|
}
|