Skip to content

Commit 943cb8c

Browse files
Refactored TUI & Included Search Functionality (#70)
* Splitted TUI Split the TUI into focused files so pkg/tui/app.go is now mostly the UI wiring/entrypoint while helpers live alongside it: + pkg/tui/state.go holds constants/enums and uiState/bucket object structs; formatters.go and table_helpers.go hold pure helpers. + View/controller logic now lives in services_view.go, buckets_view.go, and bucket_objects.go; + cross-cutting UI helpers went to dialogs.go, search.go, and auto_refresh.go. + app.go cleaned up accordingly, with redundant definitions removed and imports trimmed. * Improved search functionality + Implemented support for search via "/" across Services, Buckets, Clusters and Details panels, including substrings. * resolve conflict * resolve conflict --------- Co-authored-by: SergioLangaritaBenitez <[email protected]>
1 parent f21ee90 commit 943cb8c

File tree

11 files changed

+2269
-1890
lines changed

11 files changed

+2269
-1890
lines changed

pkg/tui/app.go

Lines changed: 65 additions & 1890 deletions
Large diffs are not rendered by default.

pkg/tui/auto_refresh.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package tui
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strconv"
7+
"strings"
8+
"time"
9+
10+
"github.com/gdamore/tcell/v2"
11+
"github.com/rivo/tview"
12+
)
13+
14+
func (s *uiState) promptAutoRefresh() {
15+
s.mutex.Lock()
16+
if s.autoRefreshPromptVisible || s.searchVisible || s.confirmVisible || s.legendVisible || s.pages == nil {
17+
s.mutex.Unlock()
18+
return
19+
}
20+
s.autoRefreshPromptVisible = true
21+
s.autoRefreshFocus = s.app.GetFocus()
22+
prevPeriod := s.autoRefreshPeriod
23+
container := s.statusContainer
24+
s.mutex.Unlock()
25+
26+
input := tview.NewInputField().
27+
SetLabel("Auto refresh seconds (0 to stop, default 10): ").
28+
SetFieldWidth(10)
29+
input.SetAcceptanceFunc(func(text string, last rune) bool {
30+
if last == 0 {
31+
return true
32+
}
33+
return last >= '0' && last <= '9'
34+
})
35+
if prevPeriod > 0 {
36+
input.SetText(fmt.Sprintf("%d", int(prevPeriod/time.Second)))
37+
}
38+
input.SetDoneFunc(func(key tcell.Key) {
39+
switch key {
40+
case tcell.KeyEnter:
41+
s.handleAutoRefreshInput(input.GetText())
42+
case tcell.KeyEscape:
43+
s.hideAutoRefreshPrompt()
44+
}
45+
})
46+
47+
s.mutex.Lock()
48+
s.autoRefreshInput = input
49+
s.mutex.Unlock()
50+
51+
s.queueUpdate(func() {
52+
container.Clear()
53+
container.SetTitle("Auto Refresh")
54+
input.SetBorder(false)
55+
container.AddItem(input, 0, 1, true)
56+
})
57+
s.app.SetFocus(input)
58+
}
59+
60+
func (s *uiState) hideAutoRefreshPrompt() {
61+
s.mutex.Lock()
62+
if !s.autoRefreshPromptVisible {
63+
s.mutex.Unlock()
64+
return
65+
}
66+
s.autoRefreshPromptVisible = false
67+
input := s.autoRefreshInput
68+
s.autoRefreshInput = nil
69+
focus := s.autoRefreshFocus
70+
s.autoRefreshFocus = nil
71+
container := s.statusContainer
72+
s.mutex.Unlock()
73+
74+
s.queueUpdate(func() {
75+
container.Clear()
76+
container.SetTitle("Status")
77+
container.AddItem(s.statusView, 0, 1, false)
78+
s.statusView.SetText(s.decorateStatusText(statusHelpText))
79+
})
80+
if focus != nil {
81+
s.app.SetFocus(focus)
82+
}
83+
if input != nil {
84+
input.SetText("")
85+
}
86+
}
87+
88+
func (s *uiState) handleAutoRefreshInput(value string) {
89+
trimmed := strings.TrimSpace(value)
90+
if trimmed == "" {
91+
s.hideAutoRefreshPrompt()
92+
s.startAutoRefresh(10 * time.Second)
93+
s.setStatus("[green]Auto refresh every 10 second(s)")
94+
return
95+
}
96+
seconds, err := strconv.Atoi(trimmed)
97+
if err != nil {
98+
s.setStatus("[red]Enter a valid number of seconds")
99+
return
100+
}
101+
if seconds < 0 {
102+
s.setStatus("[red]Refresh period must not be negative")
103+
return
104+
}
105+
106+
s.hideAutoRefreshPrompt()
107+
if seconds == 0 {
108+
if s.stopAutoRefresh() {
109+
s.setStatus("[yellow]Auto refresh disabled")
110+
} else {
111+
s.setStatus("[yellow]Auto refresh already disabled")
112+
}
113+
return
114+
}
115+
116+
period := time.Duration(seconds) * time.Second
117+
s.startAutoRefresh(period)
118+
s.setStatus(fmt.Sprintf("[green]Auto refresh every %d second(s)", seconds))
119+
}
120+
121+
func (s *uiState) startAutoRefresh(period time.Duration) {
122+
if period <= 0 {
123+
s.stopAutoRefresh()
124+
return
125+
}
126+
// Ensure previous ticker is stopped.
127+
s.stopAutoRefresh()
128+
129+
parent := context.Background()
130+
s.mutex.Lock()
131+
if s.rootCtx != nil {
132+
parent = s.rootCtx
133+
}
134+
s.mutex.Unlock()
135+
136+
ctx, cancel := context.WithCancel(parent)
137+
ticker := time.NewTicker(period)
138+
139+
s.mutex.Lock()
140+
s.autoRefreshCancel = cancel
141+
s.autoRefreshTicker = ticker
142+
s.autoRefreshPeriod = period
143+
s.autoRefreshActive = true
144+
s.mutex.Unlock()
145+
146+
go func() {
147+
s.refreshCurrent(context.Background())
148+
for {
149+
select {
150+
case <-ticker.C:
151+
s.refreshCurrent(context.Background())
152+
case <-ctx.Done():
153+
ticker.Stop()
154+
return
155+
}
156+
}
157+
}()
158+
}
159+
160+
func (s *uiState) stopAutoRefresh() bool {
161+
s.mutex.Lock()
162+
cancel := s.autoRefreshCancel
163+
active := s.autoRefreshActive
164+
s.autoRefreshCancel = nil
165+
s.autoRefreshTicker = nil
166+
s.autoRefreshPeriod = 0
167+
s.autoRefreshActive = false
168+
s.mutex.Unlock()
169+
170+
if cancel != nil {
171+
cancel()
172+
}
173+
return active
174+
}
175+
176+
func (s *uiState) decorateStatusText(base string) string {
177+
text := base
178+
s.mutex.Lock()
179+
active := s.autoRefreshActive
180+
period := s.autoRefreshPeriod
181+
s.mutex.Unlock()
182+
if active && period > 0 {
183+
seconds := int(period / time.Second)
184+
if seconds <= 0 {
185+
seconds = 1
186+
}
187+
indicator := fmt.Sprintf("[cyan]Auto refresh: every %d second(s)", seconds)
188+
if strings.TrimSpace(text) == "" {
189+
text = indicator
190+
} else {
191+
text = text + "\n" + indicator
192+
}
193+
}
194+
return text
195+
}

0 commit comments

Comments
 (0)