separate domain listing into its own model

This commit is contained in:
juancwu 2026-01-23 19:11:36 +00:00
commit 8c26134e9e
4 changed files with 162 additions and 46 deletions

View file

@ -7,6 +7,7 @@ type Page int
const ( const (
PageLogin Page = iota PageLogin Page = iota
PageMenu PageMenu
PageListDomains
) )
type SwitchPageMsg struct { type SwitchPageMsg struct {
@ -17,4 +18,7 @@ type SessionReadyMsg struct {
Client *porkbun.Client Client *porkbun.Client
} }
type ListDomainsMsg struct {
}
type ErrorMsg error type ErrorMsg error

View file

@ -6,6 +6,7 @@ import (
"git.juancwu.dev/juancwu/porkbacon/internal/config" "git.juancwu.dev/juancwu/porkbacon/internal/config"
"git.juancwu.dev/juancwu/porkbacon/internal/ui/messages" "git.juancwu.dev/juancwu/porkbacon/internal/ui/messages"
"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/login"
"git.juancwu.dev/juancwu/porkbacon/internal/ui/pages/menu" "git.juancwu.dev/juancwu/porkbacon/internal/ui/pages/menu"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
@ -15,6 +16,7 @@ type MainModel struct {
currentPage messages.Page currentPage messages.Page
login *login.Model login *login.Model
menu *menu.Model menu *menu.Model
listDomains *listdomains.Model
isMenuInit bool isMenuInit bool
width int width int
height int height int
@ -42,6 +44,10 @@ func (m MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var cmds []tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg:
if msg.String() == "ctrl+c" {
return m, tea.Quit
}
case tea.WindowSizeMsg: case tea.WindowSizeMsg:
m.width = msg.Width m.width = msg.Width
m.height = msg.Height m.height = msg.Height
@ -50,13 +56,16 @@ func (m MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
case messages.SessionReadyMsg: case messages.SessionReadyMsg:
m.menu = menu.New(msg.Client) m.menu = menu.New(msg.Client)
m.listDomains = listdomains.New(msg.Client)
if m.width > 0 && m.height > 0 { if m.width > 0 && m.height > 0 {
newMenu, _ := m.menu.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height}) newMenu, _ := m.menu.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
m.menu = newMenu.(*menu.Model) m.menu = newMenu.(*menu.Model)
} }
m.currentPage = messages.PageMenu m.currentPage = messages.PageMenu
m.isMenuInit = true m.isMenuInit = true
return m, m.menu.Init() return m, tea.Batch(m.menu.Init(), m.listDomains.Init())
case messages.ListDomainsMsg:
m.currentPage = messages.PageListDomains
} }
switch m.currentPage { switch m.currentPage {
@ -68,6 +77,10 @@ func (m MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var newMenu tea.Model var newMenu tea.Model
newMenu, cmd = m.menu.Update(msg) newMenu, cmd = m.menu.Update(msg)
m.menu = newMenu.(*menu.Model) m.menu = newMenu.(*menu.Model)
case messages.PageListDomains:
var newModel tea.Model
newModel, cmd = m.listDomains.Update(msg)
m.listDomains = newModel.(*listdomains.Model)
} }
cmds = append(cmds, cmd) cmds = append(cmds, cmd)
@ -80,6 +93,8 @@ func (m MainModel) View() string {
return m.login.View() return m.login.View()
case messages.PageMenu: case messages.PageMenu:
return m.menu.View() return m.menu.View()
case messages.PageListDomains:
return m.listDomains.View()
default: default:
return "Unknown Page" return "Unknown Page"
} }

View file

@ -0,0 +1,140 @@
package listdomains
import (
"fmt"
"strings"
"git.juancwu.dev/juancwu/porkbacon/internal/porkbun"
"git.juancwu.dev/juancwu/porkbacon/internal/ui/messages"
"github.com/charmbracelet/bubbles/paginator"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type Model struct {
loading bool
client *porkbun.Client
domains []string
paginator paginator.Model
spinner spinner.Model
stderr string
}
func New(client *porkbun.Client) *Model {
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"))
return &Model{
loading: false,
client: client,
domains: nil,
paginator: p,
spinner: s,
}
}
func (m *Model) Init() tea.Cmd {
return m.spinner.Tick
}
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
if !m.loading && msg.String() == "esc" {
return m, func() tea.Msg {
m.loading = false
m.domains = nil
m.client = nil
return messages.SwitchPageMsg{Page: messages.PageMenu}
}
}
if len(m.domains) > 0 {
m.paginator, cmd = m.paginator.Update(msg)
return m, cmd
}
case messages.ListDomainsMsg:
m.loading = true
m.domains = nil
return m, tea.Batch(listDomains(m.client), m.spinner.Tick)
case *porkbun.DomainListAllResponse:
m.loading = false
for _, domain := range msg.Domains {
m.domains = append(m.domains, renderDomain(&domain))
}
m.paginator.SetTotalPages(len(m.domains))
m.paginator.Page = 0
case messages.ErrorMsg:
m.stderr = fmt.Sprintf("Error: %v", msg)
return m, nil
}
if m.loading {
m.spinner, cmd = m.spinner.Update(msg)
return m, tea.Batch(cmd, m.spinner.Tick)
}
return m, nil
}
func (m *Model) 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.domains) > 0 {
return fmt.Sprintf("%s\n\n%s\n\n(Press Esc to go back, arrows to navigate)", m.domains[m.paginator.Page], m.paginator.View())
}
return "Uhh.. This is awkward... Press Esc to go back."
}
func listDomains(client *porkbun.Client) tea.Cmd {
return func() tea.Msg {
resp, err := client.DomainListAll(0, true)
if err != nil {
return messages.ErrorMsg(err)
}
return resp
}
}
func renderDomain(item *porkbun.Domain) string {
var b strings.Builder
b.WriteString("Domain: " + item.Domain + "\n")
b.WriteString("Status: " + item.Status + "\n")
b.WriteString("Create Date: " + item.CreateDate + "\n")
b.WriteString("Expire Date: " + item.ExpireDate + "\n")
b.WriteString("Security Lock: " + item.SecurityLock + "\n")
b.WriteString("Whois Privacy: " + item.WhoIsPrivacy + "\n")
b.WriteString(fmt.Sprintln("Auto Renew:", item.AutoRenew))
b.WriteString(fmt.Sprintln("Not Local:", item.NotLocal))
if len(item.Labels) > 0 {
b.WriteString("Labels:\n")
}
for i, label := range item.Labels {
b.WriteString("=> " + label.Title)
if i < len(item.Labels)-1 {
b.WriteString("\n")
}
}
return b.String()
}

View file

@ -17,11 +17,6 @@ type pingResultMsg struct {
IP string IP string
} }
type domainListMsg struct {
Status string
Domains []string
}
type dnsRecordListMsg struct { type dnsRecordListMsg struct {
Status string Status string
Records []string Records []string
@ -58,7 +53,6 @@ type Model struct {
textInput textinput.Model textInput textinput.Model
loading bool loading bool
state uint8 state uint8
domains []string
records []string records []string
client *porkbun.Client client *porkbun.Client
err error err error
@ -159,16 +153,6 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
if len(m.domains) > 0 {
var cmd tea.Cmd
m.paginator, cmd = m.paginator.Update(msg)
if msg.String() == "esc" {
m.domains = nil
return m, nil
}
return m, cmd
}
if len(m.records) > 0 { if len(m.records) > 0 {
var cmd tea.Cmd var cmd tea.Cmd
m.paginator, cmd = m.paginator.Update(msg) m.paginator, cmd = m.paginator.Update(msg)
@ -191,15 +175,6 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.paginator.PerPage = 1 m.paginator.PerPage = 1
m.paginator.SetTotalPages(len(msg.Records)) m.paginator.SetTotalPages(len(msg.Records))
m.paginator.Page = 0 m.paginator.Page = 0
m.domains = msg.Records
return m, nil
case domainListMsg:
m.loading = false
m.paginator.PerPage = 1
m.paginator.SetTotalPages(len(msg.Domains))
m.paginator.Page = 0
m.domains = msg.Domains
return m, nil return m, nil
case pingResultMsg: case pingResultMsg:
@ -238,10 +213,6 @@ func (m *Model) View() string {
) )
} }
if len(m.domains) > 0 {
return fmt.Sprintf("%s\n\n%s\n\n(Press Esc to go back, arrows to navigate)", m.domains[m.paginator.Page], m.paginator.View())
}
if len(m.records) > 0 { 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("%s\n\n%s\n\n(Press Esc to go back, arrows to navigate)", m.records[m.paginator.Page], m.paginator.View())
} }
@ -256,23 +227,9 @@ func (m *Model) handleSelection(i menuItem) (tea.Model, tea.Cmd) {
m.textInput.Focus() m.textInput.Focus()
return m, textinput.Blink return m, textinput.Blink
case domainListAll: case domainListAll:
m.loading = true return m, func() tea.Msg {
cmd := func() tea.Msg { return messages.ListDomainsMsg{}
resp, err := m.client.DomainListAll(0, true)
if err != nil {
return messages.ErrorMsg(err)
}
var domains []string
for _, domain := range resp.Domains {
view := renderDomainItem(&domain)
domains = append(domains, view)
}
return domainListMsg{
Status: resp.Status,
Domains: domains,
}
} }
return m, tea.Batch(cmd, m.spinner.Tick)
case utilPing: case utilPing:
m.loading = true m.loading = true
cmd := func() tea.Msg { cmd := func() tea.Msg {