diff --git a/internal/ui/messages/messages.go b/internal/ui/messages/messages.go index 79aedfb..2e93168 100644 --- a/internal/ui/messages/messages.go +++ b/internal/ui/messages/messages.go @@ -8,6 +8,7 @@ const ( PageLogin Page = iota PageMenu PageListDomains + PageDNSRetrieve ) type SwitchPageMsg struct { @@ -18,7 +19,10 @@ type SessionReadyMsg struct { Client *porkbun.Client } -type ListDomainsMsg struct { +type ListDomainsMsg struct{} + +type DNSRetrieveMsg struct { + Domain string } type ErrorMsg error diff --git a/internal/ui/model.go b/internal/ui/model.go index e8eb461..ab20255 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -6,6 +6,7 @@ import ( "git.juancwu.dev/juancwu/porkbacon/internal/config" "git.juancwu.dev/juancwu/porkbacon/internal/ui/messages" + "git.juancwu.dev/juancwu/porkbacon/internal/ui/pages/dns" "git.juancwu.dev/juancwu/porkbacon/internal/ui/pages/listdomains" "git.juancwu.dev/juancwu/porkbacon/internal/ui/pages/login" "git.juancwu.dev/juancwu/porkbacon/internal/ui/pages/menu" @@ -17,6 +18,7 @@ type MainModel struct { login *login.Model menu *menu.Model listDomains *listdomains.Model + dnsRetrieve dns.RetrieveModel isMenuInit bool width int height int @@ -57,17 +59,21 @@ func (m MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case messages.SessionReadyMsg: m.menu = menu.New(msg.Client) m.listDomains = listdomains.New(msg.Client) + m.dnsRetrieve = dns.NewRetrieveModel(msg.Client) if m.width > 0 && m.height > 0 { newMenu, _ := m.menu.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height}) m.menu = newMenu.(*menu.Model) } m.currentPage = messages.PageMenu m.isMenuInit = true - return m, tea.Batch(m.menu.Init(), m.listDomains.Init()) + return m, tea.Batch(m.menu.Init(), m.listDomains.Init(), m.dnsRetrieve.Init()) case messages.ListDomainsMsg: m.currentPage = messages.PageListDomains + case messages.DNSRetrieveMsg: + m.currentPage = messages.PageDNSRetrieve } + var newModel tea.Model switch m.currentPage { case messages.PageLogin: var newLogin tea.Model @@ -78,9 +84,11 @@ func (m MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { newMenu, cmd = m.menu.Update(msg) m.menu = newMenu.(*menu.Model) case messages.PageListDomains: - var newModel tea.Model newModel, cmd = m.listDomains.Update(msg) m.listDomains = newModel.(*listdomains.Model) + case messages.PageDNSRetrieve: + newModel, cmd = m.dnsRetrieve.Update(msg) + m.dnsRetrieve = newModel.(dns.RetrieveModel) } cmds = append(cmds, cmd) @@ -95,6 +103,8 @@ func (m MainModel) View() string { return m.menu.View() case messages.PageListDomains: return m.listDomains.View() + case messages.PageDNSRetrieve: + return m.dnsRetrieve.View() default: return "Unknown Page" } diff --git a/internal/ui/pages/dns/retrieve.go b/internal/ui/pages/dns/retrieve.go new file mode 100644 index 0000000..15592ca --- /dev/null +++ b/internal/ui/pages/dns/retrieve.go @@ -0,0 +1,157 @@ +package dns + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/paginator" + "github.com/charmbracelet/bubbles/spinner" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + + "git.juancwu.dev/juancwu/porkbacon/internal/porkbun" + "git.juancwu.dev/juancwu/porkbacon/internal/ui/messages" + "git.juancwu.dev/juancwu/porkbacon/internal/ui/utils" +) + +type RetrieveModel struct { + client *porkbun.Client + loading bool + records []string + + spinner spinner.Model + paginator paginator.Model + textinput textinput.Model + + stderr string +} + +func NewRetrieveModel(client *porkbun.Client) RetrieveModel { + p := paginator.New() + p.Type = paginator.Dots + p.PerPage = 1 + p.ActiveDot = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "235", Dark: "252"}).Render("•") + p.InactiveDot = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "250", Dark: "238"}).Render("•") + + s := spinner.New() + s.Spinner = spinner.Dot + s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205")) + + ti := textinput.New() + ti.Placeholder = "Enter domain" + ti.Width = 80 + + return RetrieveModel{ + client: client, + spinner: s, + paginator: p, + textinput: ti, + } +} + +func (m RetrieveModel) Init() tea.Cmd { + return tea.Batch(m.spinner.Tick, textinput.Blink) +} + +func (m RetrieveModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.KeyMsg: + if m.loading { + return m, nil + } + + if msg.String() == "esc" { + hadRecords := len(m.records) > 0 + m.loading = false + m.records = nil + return m, func() tea.Msg { + if hadRecords { + return messages.DNSRetrieveMsg{} + } + + return messages.SwitchPageMsg{Page: messages.PageMenu} + } + } + + if msg.String() == "enter" { + m.loading = true + m.records = nil + return m, retrieveRecords(m.client, m.textinput.Value()) + } + + if len(m.records) > 0 { + m.paginator, cmd = m.paginator.Update(msg) + return m, cmd + } + + m.textinput, cmd = m.textinput.Update(msg) + return m, cmd + + case messages.DNSRetrieveMsg: + m.textinput.Reset() + m.textinput.Focus() + + case *porkbun.DNSRecordsResponse: + m.loading = false + for _, record := range msg.Records { + m.records = append(m.records, renderRecord(&record)) + } + m.paginator.SetTotalPages(len(m.records)) + m.paginator.Page = 0 + case messages.ErrorMsg: + m.stderr = fmt.Sprintf("Error: %v", msg) + } + + if m.loading { + m.spinner, cmd = m.spinner.Update(msg) + return m, tea.Batch(cmd, m.spinner.Tick) + } + + return m, textinput.Blink +} + +func (m RetrieveModel) View() string { + if m.stderr != "" { + return fmt.Sprintf("%s\n\n(Press ctrl+c to quit)", m.stderr) + } + + if m.loading { + return fmt.Sprintf("\n\n %s Loading... press ctl+c to quit\n\n", m.spinner.View()) + } + + if len(m.records) > 0 { + return fmt.Sprintf("%s\n\n%s\n\n(Press Esc to go back, arrows to navigate)", m.records[m.paginator.Page], m.paginator.View()) + } + + return fmt.Sprintf( + "Enter domain to retrieve records for:\n\n%s\n\n(esc to quit)", + m.textinput.View(), + ) +} + +func retrieveRecords(client *porkbun.Client, domain string) tea.Cmd { + return func() tea.Msg { + resp, err := client.RetrieveDNSRecords(domain) + if err != nil { + return messages.ErrorMsg(err) + } + return resp + } +} + +func renderRecord(item *porkbun.DNSRecord) string { + var b strings.Builder + b.WriteString("ID: " + item.ID + "\n") + b.WriteString("Name: " + item.Name + "\n") + b.WriteString("Type: " + item.Type + "\n") + b.WriteString(fmt.Sprintln("TTL:", item.TTL)) + b.WriteString(fmt.Sprintln("Priority:", item.Priority)) + b.WriteString("Content: ") + b.WriteString(utils.WrapText(item.Content, 80)) + b.WriteString("\n") + b.WriteString("Notes: " + item.Notes + "\n") + return b.String() +} diff --git a/internal/ui/pages/listdomains/model.go b/internal/ui/pages/listdomains/model.go index a4e2784..0b9ccec 100644 --- a/internal/ui/pages/listdomains/model.go +++ b/internal/ui/pages/listdomains/model.go @@ -51,10 +51,9 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: if !m.loading && msg.String() == "esc" { + m.loading = false + m.domains = nil return m, func() tea.Msg { - m.loading = false - m.domains = nil - m.client = nil return messages.SwitchPageMsg{Page: messages.PageMenu} } } diff --git a/internal/ui/pages/menu/model.go b/internal/ui/pages/menu/model.go index c1b2112..9dbd1b8 100644 --- a/internal/ui/pages/menu/model.go +++ b/internal/ui/pages/menu/model.go @@ -17,11 +17,6 @@ type pingResultMsg struct { IP string } -type dnsRecordListMsg struct { - Status string - Records []string -} - type menuItem struct { id uint8 title, desc string @@ -53,7 +48,6 @@ type Model struct { textInput textinput.Model loading bool state uint8 - records []string client *porkbun.Client err error output string @@ -109,42 +103,6 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil case tea.KeyMsg: - if m.loading && msg.String() == "ctrl+c" { - return m, tea.Quit - } - - if m.state == stateDNSRetrieveGetDomain { - switch msg.String() { - case "enter": - domain := m.textInput.Value() - m.textInput.SetValue("") - m.state = stateNoOp - m.loading = true - cmd := func() tea.Msg { - resp, err := m.client.RetrieveDNSRecords(domain) - if err != nil { - return messages.ErrorMsg(err) - } - var records []string - for _, record := range resp.Records { - records = append(records, renderRecordItem(&record)) - } - return dnsRecordListMsg{ - Status: resp.Status, - Records: records, - } - } - return m, tea.Batch(cmd, m.spinner.Tick) - case "esc": - m.state = stateNoOp - m.textInput.SetValue("") - return m, nil - } - var cmd tea.Cmd - m.textInput, cmd = m.textInput.Update(msg) - return m, cmd - } - if m.output != "" { if msg.String() == "esc" { m.output = "" @@ -153,16 +111,6 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } - if len(m.records) > 0 { - var cmd tea.Cmd - m.paginator, cmd = m.paginator.Update(msg) - if msg.String() == "esc" { - m.records = nil - return m, nil - } - return m, cmd - } - if msg.String() == "enter" { i, ok := m.list.SelectedItem().(menuItem) if ok { @@ -170,13 +118,6 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } - case dnsRecordListMsg: - m.loading = false - m.paginator.PerPage = 1 - m.paginator.SetTotalPages(len(msg.Records)) - m.paginator.Page = 0 - return m, nil - case pingResultMsg: m.loading = false m.output = fmt.Sprintf("Ping successful!\nYour IP: %s", msg.IP) @@ -213,19 +154,15 @@ func (m *Model) View() string { ) } - if len(m.records) > 0 { - return fmt.Sprintf("%s\n\n%s\n\n(Press Esc to go back, arrows to navigate)", m.records[m.paginator.Page], m.paginator.View()) - } - return m.list.View() } func (m *Model) handleSelection(i menuItem) (tea.Model, tea.Cmd) { switch i.id { case dnsRetrieveRecords: - m.state = stateDNSRetrieveGetDomain - m.textInput.Focus() - return m, textinput.Blink + return m, func() tea.Msg { + return messages.DNSRetrieveMsg{} + } case domainListAll: return m, func() tea.Msg { return messages.ListDomainsMsg{}