11package debug
22
33import (
4+ "fmt"
45 "strconv"
56
67 "github.com/charmbracelet/bubbles/key"
78 "github.com/charmbracelet/lipgloss"
89 "github.com/charmbracelet/lipgloss/table"
10+ log "github.com/sirupsen/logrus"
911
1012 "github.com/mintoolkit/mint/pkg/app"
1113 "github.com/mintoolkit/mint/pkg/app/master/command"
1214 "github.com/mintoolkit/mint/pkg/app/master/tui/common"
1315 "github.com/mintoolkit/mint/pkg/app/master/tui/keys"
16+ "github.com/mintoolkit/mint/pkg/crt"
1417
1518 tea "github.com/charmbracelet/bubbletea"
1619)
@@ -24,8 +27,14 @@ type TUI struct {
2427 table table.Table
2528
2629 showDebuggableContainers bool
30+ showRuntimeSelectorView bool
2731
2832 gcvalues * command.GenericParams
33+
34+ // runtime selection controls
35+ choice int
36+
37+ runtime string
2938}
3039
3140// Styles - move to `common`
@@ -46,26 +55,20 @@ var (
4655 EvenRowStyle = CellStyle .Foreground (lightGray )
4756 // BorderStyle is the lipgloss style used for the table border.
4857 BorderStyle = lipgloss .NewStyle ().Foreground (white )
58+ // CheckboxStyle is the lipgloss style used for the runtime selector
59+ CheckboxStyle = lipgloss .NewStyle ().Foreground (lipgloss .Color ("212" ))
4960)
5061
5162// End Styles - move to common - block
5263
53- func LoadTUI () * TUI {
54- m := & TUI {
55- width : 20 ,
56- height : 15 ,
57- loading : true ,
58- }
59- return m
60- }
61-
6264// InitialTUI returns the initial state of the model.
6365func InitialTUI (standalone bool , gcvalues * command.GenericParams ) * TUI {
6466 m := & TUI {
6567 standalone : standalone ,
6668 width : 20 ,
6769 height : 15 ,
6870 gcvalues : gcvalues ,
71+ runtime : crt .AutoSelectRuntime (),
6972 }
7073
7174 return m
@@ -85,32 +88,29 @@ type DebuggableContainer struct {
8588func (m TUI ) Update (msg tea.Msg ) (tea.Model , tea.Cmd ) {
8689 switch msg := msg .(type ) {
8790 case common.Event :
88- debuggableContainersCh := make (chan interface {})
91+ subscriptionChannel := make (chan interface {})
8992 // NOTE -> the names of both the channel map and the channel are misleading
9093 // as more than just the debuggable container information is dumped on it
9194 // at the moment.
92- debuggableContainersChannelMap := map [string ]chan interface {}{
93- "debuggableContainers" : debuggableContainersCh ,
95+ subscriptionChannels := map [string ]chan interface {}{
96+ "debuggableContainers" : subscriptionChannel ,
9497 }
9598 // In addition to passing the channel(s) we will use to transport data
9699 // we should pass:
97100 // the outputs we want to subscribe to: State | Info | Error
98101 xc := app .NewExecutionContext (
99102 "tui" ,
100- // Quiet -> when set to true, returns on the first line for each
101- // Execution context method
102103 true ,
103104 "subscription" ,
104- debuggableContainersChannelMap ,
105+ subscriptionChannels ,
105106 )
106107
107108 cparams := & CommandParams {
108- // NOTE -> should not always pass docker here.
109- Runtime : "docker" ,
110- // Note -> we should not pass this by default, and instead pass it when a user asks.
109+ Runtime : m .runtime ,
110+ // Passing these three fields all the time does not make sense.
111111 ActionListDebuggableContainers : true ,
112- // How to pass the target ref:
113- // TargetRef: "my-nginx"
112+ Kubeconfig : crt . KubeconfigDefault ,
113+ TargetNamespace : "default" ,
114114 }
115115
116116 gcValue , ok := msg .Data .(* command.GenericParams )
@@ -126,21 +126,38 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
126126
127127 doneCh := make (chan struct {})
128128 go func () {
129- for debuggableContainersData := range debuggableContainersCh {
130- channelResponse , ok := debuggableContainersData .(map [string ]string )
129+ for subscriptionData := range subscriptionChannel {
130+ channelResponse , ok := subscriptionData .(map [string ]string )
131131 if ! ok || channelResponse == nil {
132132 continue
133133 }
134+
135+ log .Debugf ("Channel response in tui: %v" , channelResponse )
136+ stateValue , stateExists := channelResponse ["state" ]
137+ if stateExists {
138+ log .Debugf ("State value: %s" , stateValue )
139+ if stateValue == "kubernetes.runtime.handler.started" {
140+ // TODO - what would we like to do with this information?
141+ // && we likely will want to add similar handling for the other runtimes.
142+ } else if stateValue == "completed" {
143+ // We get 'completed' then we get 'done'
144+ log .Debug ("Exiting channel listening loop in update. State is complete." )
145+ break
146+ }
147+ }
148+
134149 infoValue , infoExists := channelResponse ["info" ]
135150 if infoExists {
136- // Set total debuggable container counter ceiling
137151 if infoValue == "debuggable.containers" && counterCeiling == 0 {
152+ // Start docker runtime driven debuggable container handling
153+ // Set total debuggable container counter ceiling
138154 countInt , err := strconv .Atoi (channelResponse ["count" ])
139155 if err != nil {
140156 continue
141157 }
142158 counterCeiling = countInt
143159 } else if infoValue == "debuggable.container" {
160+ log .Debugln ("-----------------------Got debuggable container-----------------------" )
144161 debuggableContainers = append (debuggableContainers , DebuggableContainer {
145162 Name : channelResponse ["name" ],
146163 Image : channelResponse ["image" ],
@@ -149,9 +166,12 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
149166 }
150167 }
151168
169+ // The notion of a count[er] does not exist for the k8s
170+ // But it does for podman & docker.
152171 if counterCeiling > 0 && counter == counterCeiling {
153172 break
154173 }
174+ // End debuggable container handling
155175 }
156176 m .table = generateTable (debuggableContainers )
157177 close (doneCh )
@@ -164,25 +184,48 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
164184 switch {
165185 case key .Matches (msg , keys .Global .Quit ):
166186 return m , tea .Quit
167- // NOTE -> We should only support this back navigation,
168- // if the tui is not in standalone mode.
169187 case key .Matches (msg , keys .Global .Back ):
188+ if m .standalone {
189+ return m , nil
190+ }
170191 return common .TUIsInstance .Home , nil
171192 case key .Matches (msg , keys .Debug .LoadDebuggableContainers ):
172- // Kickoff loading of debuggable containers in standalone mode.
173- if m .standalone {
174- loadDebuggableContainers := common.Event {
175- Type : common .LaunchDebugEvent ,
176- Data : m .gcvalues ,
177- }
178- m , _ := m .Update (loadDebuggableContainers )
193+ if m .showRuntimeSelectorView {
179194 return m , nil
180195 }
181-
182- // When used via `tui -> debug`
183- m .showDebuggableContainers = ! m .showDebuggableContainers
196+ loadDebuggableContainers := common.Event {
197+ Type : common .LaunchDebugEvent ,
198+ Data : m .gcvalues ,
199+ }
200+ m , _ := m .Update (loadDebuggableContainers )
184201 return m , nil
202+ case key .Matches (msg , keys .Debug .ChangeRuntime ):
203+ m .showDebuggableContainers = false
204+ m .showRuntimeSelectorView = ! m .showRuntimeSelectorView
205+ return m , nil
206+ }
207+ }
208+ return updateChoices (msg , m )
209+ }
185210
211+ func updateChoices (msg tea.Msg , m TUI ) (tea.Model , tea.Cmd ) {
212+ switch msg := msg .(type ) {
213+ case tea.KeyMsg :
214+ switch msg .String () {
215+ case "j" , "down" :
216+ m .choice ++
217+ if m .choice > 3 {
218+ m .choice = 0
219+ }
220+ case "k" , "up" :
221+ m .choice --
222+ if m .choice < 0 {
223+ m .choice = 3
224+ }
225+ case "enter" :
226+ m .runtime = setNewRuntime (m .choice )
227+ m .showRuntimeSelectorView = false
228+ return m , nil
186229 }
187230 }
188231 return m , nil
@@ -218,6 +261,48 @@ func generateTable(debuggableContainers []DebuggableContainer) table.Table {
218261 return * t
219262}
220263
264+ func choicesView (m TUI ) string {
265+ choice := m .choice
266+
267+ template := "Choose runtime for debug mode\n \n "
268+ template += "%s\n \n "
269+
270+ // NOTE -> the chocies we display here should only be runtiems we can
271+ // establish a connection to.
272+ // Otherwise, we set the user up for failure.
273+
274+ choices := fmt .Sprintf (
275+ "%s\n %s\n %s\n %s" ,
276+ checkbox ("Docker" , choice == 0 ),
277+ checkbox ("Containerd" , choice == 1 ),
278+ checkbox ("Podman" , choice == 2 ),
279+ checkbox ("Kubernetes" , choice == 3 ),
280+ )
281+ return fmt .Sprintf (template , choices )
282+ }
283+
284+ func checkbox (label string , checked bool ) string {
285+ if checked {
286+ return CheckboxStyle .Render ("[x] " + label )
287+ }
288+ return fmt .Sprintf ("[ ] %s" , label )
289+ }
290+
291+ func setNewRuntime (choice int ) string {
292+ switch choice {
293+ case 0 :
294+ return crt .DockerRuntime
295+ case 1 :
296+ return crt .ContainerdRuntime
297+ case 2 :
298+ return crt .PodmanRuntime
299+ case 3 :
300+ return crt .KubernetesRuntime
301+ default :
302+ return crt .AutoRuntime
303+ }
304+ }
305+
221306// View returns the view that should be displayed.
222307func (m TUI ) View () string {
223308 var components []string
@@ -229,15 +314,23 @@ func (m TUI) View() string {
229314 // 4. Connect to a debug session
230315 // 5. Start a new debug session
231316
232- content := "Debug Dashboard\n "
317+ header := "Debug Dashboard\n "
318+
319+ currentRuntime := fmt .Sprintf ("Current Runtime: %s.\n " , m .runtime )
233320
234- components = append (components , content )
321+ components = append (components , header , currentRuntime )
235322
236323 if m .showDebuggableContainers {
237324 header := "Debuggable Containers\n "
238325 components = append (components , header , m .table .String ())
239326 }
240327
328+ if m .showRuntimeSelectorView {
329+ var runtimeSelectorContent string
330+ runtimeSelectorContent = choicesView (m )
331+ components = append (components , runtimeSelectorContent )
332+ }
333+
241334 components = append (components , m .help ())
242335
243336 return lipgloss .JoinVertical (lipgloss .Left ,
@@ -246,17 +339,26 @@ func (m TUI) View() string {
246339}
247340
248341func (m TUI ) help () string {
249- var listOrHide string
342+ var debuggableContainersHelp , runtimeSelectorHelp string
250343
251- if m .showDebuggableContainers {
252- listOrHide = "hide"
344+ if m .showRuntimeSelectorView {
345+ // Only display the navigation controls if the using is changing their runtime
346+ runtimeSelectorHelp = "cancel • j/k, up/down: select • enter: choose"
253347 } else {
254- listOrHide = "list"
348+ runtimeSelectorHelp = "change runtime"
349+
350+ // Hide debuggable container help when selecting runtime
351+ if m .showDebuggableContainers {
352+ debuggableContainersHelp = "• l: hide debuggable containers"
353+ } else {
354+ debuggableContainersHelp = "• l: list debuggable containers"
355+ }
356+
255357 }
256358
257359 if m .standalone {
258- return common .HelpStyle ("• l : " + listOrHide + " debuggable containers • q: quit" )
360+ return common .HelpStyle (debuggableContainersHelp + " • r : " + runtimeSelectorHelp + " • q: quit" )
259361 }
260362
261- return common .HelpStyle ("• l : " + listOrHide + " debuggable containers • esc: back • q: quit" )
363+ return common .HelpStyle (debuggableContainersHelp + " • r : " + runtimeSelectorHelp + " • esc: back • q: quit" )
262364}
0 commit comments