0.1.21 fbfa93c

spinner.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close