1package mvu
2
3import (
4 "crypto/tls"
5 "fmt"
6 "net/http"
7 "time"
8
9 "github.com/charmbracelet/bubbles/spinner"
10 tea "github.com/charmbracelet/bubbletea"
11 "github.com/charmbracelet/lipgloss"
12)
13
14const (
15 defaultMaxRetries = 20
16 httpTimeout = 10 * time.Second
17 retryInterval = 1 * time.Second
18)
19
20type SpinnerModel struct {
21 spinner spinner.Model
22 quitting bool
23 err error
24 deployment DeploymentInfo
25 DeploymentDone bool
26}
27
28type DeploymentInfo struct {
29 imageName string
30 targetDomain string
31 port string
32 client *http.Client
33 retryCount int
34 maxRetries int
35}
36
37type urlCheckedMsg struct {
38 success bool
39 err error
40}
41
42// NewSpinnerModel returns a new SpinnerModel
43func NewSpinnerModel() SpinnerModel {
44 s := spinner.New()
45 s.Spinner = spinner.Dot
46 s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("#54baff"))
47
48 return SpinnerModel{
49 spinner: s,
50 deployment: DeploymentInfo{
51 maxRetries: defaultMaxRetries,
52 client: newHTTPClient(),
53 },
54 }
55}
56
57func newHTTPClient() *http.Client {
58 return &http.Client{
59 Timeout: httpTimeout,
60 Transport: &http.Transport{
61 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
62 },
63 }
64}
65
66func (m SpinnerModel) Init() tea.Cmd {
67 return tea.Batch(m.spinner.Tick, m.checkURL())
68}
69
70// checkURL returns a command that checks the URL and returns a urlCheckedMsg
71func (m *SpinnerModel) checkURL() tea.Cmd {
72 return func() tea.Msg {
73 success, err := attemptURLCheck(m.deployment)
74 if err != nil {
75 return urlCheckedMsg{success: false, err: err}
76 }
77 return urlCheckedMsg{success: success, err: nil}
78 }
79}
80
81// attemptURLCheck attempts to check the URL and returns a bool and error
82func attemptURLCheck(deployment DeploymentInfo) (bool, error) {
83 for deployment.retryCount = 0; deployment.retryCount < deployment.maxRetries; deployment.retryCount++ {
84 resp, err := deployment.client.Get(deployment.targetDomain)
85 if err != nil {
86 return false, fmt.Errorf("error checking URL: %w", err)
87 }
88 resp.Body.Close()
89 if resp.StatusCode == http.StatusOK {
90 return true, nil
91 }
92 time.Sleep(retryInterval)
93 }
94 return false, fmt.Errorf("the URL %s could not be reached after %d attempts", deployment.targetDomain, deployment.retryCount)
95}
96
97// Update handles the messages sent to the model
98func (m SpinnerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
99 switch msg := msg.(type) {
100 case tea.KeyMsg:
101 switch msg.String() {
102 case "q", "esc", "ctrl+c":
103 m.quitting = true
104 return m, tea.Quit
105 default:
106 return m, nil
107 }
108 case urlCheckedMsg:
109 if msg.success {
110 m.DeploymentDone = true
111 return m, tea.Quit
112 } else {
113 m.err = msg.err
114 return m, nil
115 }
116 case spinner.TickMsg:
117 var cmd tea.Cmd
118 m.spinner, cmd = m.spinner.Update(msg)
119 return m, cmd
120
121 default:
122 return m, nil
123 }
124}
125
126// View returns the view for the model
127func (m SpinnerModel) View() string {
128 if m.quitting {
129 if m.err != nil {
130 return fmt.Sprintf("Error: %v\n", m.err)
131 }
132 return "Deployment cancelled.\n"
133 }
134
135 // Use the textStyle to format the entire string
136 return fmt.Sprintf("\n\n %s Wait while Traefik is setting up the domain and certificates... \n\n", m.spinner.View())
137}