From 67b46987b35539188fad62ffa23a297d825e8bd9 Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sat, 26 Oct 2024 21:39:26 -0400 Subject: [PATCH 01/14] Move data channel out of handler Signed-off-by: Evan Harris --- pkg/app/execontext.go | 9 +++++++++ pkg/app/master/command/images/handler.go | 3 +-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/app/execontext.go b/pkg/app/execontext.go index dd726f3f..902ea3e3 100644 --- a/pkg/app/execontext.go +++ b/pkg/app/execontext.go @@ -272,6 +272,15 @@ func (ref *Output) Message(data string) { } +func (ref *Output) Data(channelKey string, data interface{}) { + if ch, exists := ref.DataChannels[channelKey]; exists { + ch <- data // Send data to the corresponding channel + fmt.Printf("Data sent to channel '%s': %v\n", channelKey, data) + } else { + fmt.Printf("Channel for channelKey '%s' not found\n", channelKey) + } +} + func (ref *Output) State(state string, params ...OutVars) { if ref.Quiet { return diff --git a/pkg/app/master/command/images/handler.go b/pkg/app/master/command/images/handler.go index e7ba1394..ab12e7b1 100644 --- a/pkg/app/master/command/images/handler.go +++ b/pkg/app/master/command/images/handler.go @@ -136,8 +136,7 @@ func OnCommand( } else if cparams.GlobalTUI { // `tui` -> `i` // TODO - create a central store for the lookup key. // As this key needs to be the same on the sender and the receiver. - xc.Out.DataChannels["images"] <- images - close(xc.Out.DataChannels["images"]) + xc.Out.Data("images", images) } else if xc.Out.Quiet { if xc.Out.OutputFormat == command.OutputFormatJSON { fmt.Printf("%s\n", jsonutil.ToPretty(images)) From ee4625412bcff11ae0e1cae9dca0168a9c4098dc Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sun, 27 Oct 2024 19:32:57 -0400 Subject: [PATCH 02/14] Mark TODO Signed-off-by: Evan Harris --- pkg/app/master/command/images/flags.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/app/master/command/images/flags.go b/pkg/app/master/command/images/flags.go index 77c80e86..296e27aa 100644 --- a/pkg/app/master/command/images/flags.go +++ b/pkg/app/master/command/images/flags.go @@ -9,8 +9,9 @@ import ( const ( FlagFilter = "filter" FlagFilterUsage = "container image filter pattern" - FlagTUI = "tui" - FlagTUIUsage = "terminal user interface" + // TODO - replace with reference to `master/command.FlagTUI` + FlagTUI = "tui" + FlagTUIUsage = "terminal user interface" ) var Flags = map[string]cli.Flag{ From 47a95274a4931d43d42a540dbeb698e53ece67f3 Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sun, 27 Oct 2024 19:33:13 -0400 Subject: [PATCH 03/14] Add debug key bindings Signed-off-by: Evan Harris --- pkg/app/master/tui/keys/keys.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/app/master/tui/keys/keys.go b/pkg/app/master/tui/keys/keys.go index 1f578859..9bf9fba4 100644 --- a/pkg/app/master/tui/keys/keys.go +++ b/pkg/app/master/tui/keys/keys.go @@ -31,6 +31,10 @@ type home struct { Debug key.Binding } +type debug struct { + LoadDebuggableContainers key.Binding +} + var Home = home{ Images: key.NewBinding( key.WithKeys("i"), @@ -41,3 +45,10 @@ var Home = home{ key.WithHelp("d", "Open debug view"), ), } + +var Debug = debug{ + LoadDebuggableContainers: key.NewBinding( + key.WithKeys("l"), + key.WithHelp("l", "Load debuggable containers"), + ), +} From 61a36f5957f983378c68bfd5ea2c19e9bd6a0d0c Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sun, 27 Oct 2024 19:33:51 -0400 Subject: [PATCH 04/14] Add TUI flags Signed-off-by: Evan Harris --- pkg/app/master/command/cliflags.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/app/master/command/cliflags.go b/pkg/app/master/command/cliflags.go index 91e96ea8..212c06b6 100644 --- a/pkg/app/master/command/cliflags.go +++ b/pkg/app/master/command/cliflags.go @@ -312,6 +312,10 @@ const ( FlagContainerDNSSearchUsage = "Add a dns search domain for unqualified hostnames analyzing image at runtime" FlagMountUsage = "Mount volume analyzing image" FlagDeleteFatImageUsage = "Delete generated fat image requires --dockerfile flag" + + // TUI Related flags + FlagTUI = "tui" + FlagTUIUsage = "Enable terminal user interface mode" ) // Container runtime command flag names and usage descriptions @@ -1026,6 +1030,12 @@ var CommonFlags = map[string]cli.Flag{ Usage: FlagRuntimeUsage, EnvVars: []string{"DSLIM_CRT_NAME"}, }, + // TUI Mode + FlagTUI: &cli.BoolFlag{ + Name: FlagTUI, + Usage: FlagTUIUsage, + EnvVars: []string{"DSLIM_TUI"}, + }, } //var CommonFlags From 8cc0de01893a35ddbc32bf6b8fadb6fc890a25c1 Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sun, 27 Oct 2024 19:34:09 -0400 Subject: [PATCH 05/14] Add TUI flags to debug cli Signed-off-by: Evan Harris --- pkg/app/master/command/debug/cli.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/app/master/command/debug/cli.go b/pkg/app/master/command/debug/cli.go index 84e1f9e6..fd724632 100644 --- a/pkg/app/master/command/debug/cli.go +++ b/pkg/app/master/command/debug/cli.go @@ -86,6 +86,10 @@ type CommandParams struct { UseSecurityContextFromTarget bool /// fallback to using target container user if it's non-root (mostly for kubernetes) DoFallbackToTargetUser bool + // `debug --tui` use mode` + TUI bool + // tui -> debug use mode + GlobalTUI bool } func ParseNameValueList(list []string) []NVPair { @@ -166,6 +170,7 @@ var CLI = &cli.Command{ cflag(FlagRunPrivileged), cflag(FlagSecurityContextFromTarget), cflag(FlagFallbackToTargetUser), + command.Cflag(command.FlagTUI), }, Action: func(ctx *cli.Context) error { gcvalues := command.GlobalFlagValues(ctx) @@ -209,6 +214,7 @@ var CLI = &cli.Command{ DoRunPrivileged: ctx.Bool(FlagRunPrivileged), UseSecurityContextFromTarget: ctx.Bool(FlagSecurityContextFromTarget), DoFallbackToTargetUser: ctx.Bool(FlagFallbackToTargetUser), + TUI: ctx.Bool(command.FlagTUI), } if commandParams.ActionListNamespaces && From ad306bfb85e942a4e8ad6160a935557d691b1d01 Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sun, 27 Oct 2024 19:34:31 -0400 Subject: [PATCH 06/14] Add debug event Signed-off-by: Evan Harris --- pkg/app/master/tui/common/event.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/app/master/tui/common/event.go b/pkg/app/master/tui/common/event.go index 4adcb8b9..d2262621 100644 --- a/pkg/app/master/tui/common/event.go +++ b/pkg/app/master/tui/common/event.go @@ -1,7 +1,8 @@ package common const ( - GetImagesEvent EventType = "getImages" + GetImagesEvent EventType = "getImages" + LaunchDebugEvent EventType = "launchDebug" ) type ( From 84793968f0f8c65055825e15bb4a9ca8c8c75f66 Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sun, 27 Oct 2024 19:36:40 -0400 Subject: [PATCH 07/14] Implement debug tui with channel based data flow Signed-off-by: Evan Harris --- pkg/app/execontext.go | 37 +++- pkg/app/master/command/debug/handler.go | 7 + pkg/app/master/command/debug/tui.go | 239 ++++++++++++++++++++++++ pkg/app/master/tui/debug/model.go | 67 ------- 4 files changed, 273 insertions(+), 77 deletions(-) create mode 100644 pkg/app/master/command/debug/tui.go delete mode 100644 pkg/app/master/tui/debug/model.go diff --git a/pkg/app/execontext.go b/pkg/app/execontext.go index 902ea3e3..03f83aa5 100644 --- a/pkg/app/execontext.go +++ b/pkg/app/execontext.go @@ -18,6 +18,7 @@ import ( const ( ofJSON = "json" ofText = "text" + ofData = "data" ) type ExecutionContext struct { @@ -122,20 +123,34 @@ func NewExecutionContext( } type Output struct { - CmdName string - Quiet bool - OutputFormat string - DataChannels map[string]chan interface{} + CmdName string + Quiet bool + OutputFormat string + DataChannels map[string]chan interface{} + internalDataCh chan interface{} } func NewOutput(cmdName string, quiet bool, outputFormat string, channels map[string]chan interface{}) *Output { ref := &Output{ - CmdName: cmdName, - Quiet: quiet, - OutputFormat: outputFormat, - DataChannels: channels, + CmdName: cmdName, + Quiet: quiet, + OutputFormat: outputFormat, + DataChannels: channels, + internalDataCh: make(chan interface{}), } + // We want to listen to the internal channel for any data + // And dump it onto the appropraite DataChannels + go func() { + for data := range ref.internalDataCh { + if data != nil { + for _, ch := range ref.DataChannels { + ch <- data + } + } + } + }() + return ref } @@ -353,7 +368,8 @@ var ( ) func (ref *Output) Info(infoType string, params ...OutVars) { - if ref.Quiet { + // TODO - carry this pattern to other Output methods + if ref.Quiet && ref.OutputFormat != ofData { return } @@ -388,7 +404,8 @@ func (ref *Output) Info(infoType string, params ...OutVars) { fmt.Println(string(jsonData)) case ofText: fmt.Printf("cmd=%s info=%s%s%s\n", ref.CmdName, itcolor(infoType), sep, data) - + case ofData: + ref.internalDataCh <- msg // Send data to the internal channel default: log.Fatalf("Unknown console output flag: %s\n. It should be either 'text' or 'json", ref.OutputFormat) } diff --git a/pkg/app/master/command/debug/handler.go b/pkg/app/master/command/debug/handler.go index 3d5233ac..73f289af 100644 --- a/pkg/app/master/command/debug/handler.go +++ b/pkg/app/master/command/debug/handler.go @@ -7,6 +7,7 @@ import ( "github.com/mintoolkit/mint/pkg/app" "github.com/mintoolkit/mint/pkg/app/master/command" + "github.com/mintoolkit/mint/pkg/app/master/tui" "github.com/mintoolkit/mint/pkg/app/master/version" cmd "github.com/mintoolkit/mint/pkg/command" "github.com/mintoolkit/mint/pkg/crt" @@ -82,6 +83,12 @@ func OnCommand( resolved := command.ResolveAutoRuntime(commandParams.Runtime) logger.Tracef("runtime.handler: rt=%s resolved=%s", commandParams.Runtime, resolved) + + if commandParams.TUI { // `debug --tui`` + initialTUI := InitialTUI(true) + tui.RunTUI(initialTUI, true) + } + switch resolved { case crt.DockerRuntime: client, err := dockerclient.New(gparams.ClientConfig) diff --git a/pkg/app/master/command/debug/tui.go b/pkg/app/master/command/debug/tui.go new file mode 100644 index 00000000..64c21a6e --- /dev/null +++ b/pkg/app/master/command/debug/tui.go @@ -0,0 +1,239 @@ +package debug + +import ( + "strconv" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/table" + + "github.com/mintoolkit/mint/pkg/app" + "github.com/mintoolkit/mint/pkg/app/master/command" + "github.com/mintoolkit/mint/pkg/app/master/tui/common" + "github.com/mintoolkit/mint/pkg/app/master/tui/keys" + + tea "github.com/charmbracelet/bubbletea" +) + +// TUI represents the internal state of the terminal user interface. +type TUI struct { + width int + height int + standalone bool + loading bool + table table.Table + + showDebuggableContainers bool +} + +// Styles - move to `common` +const ( + gray = lipgloss.Color("#737373") + lightGray = lipgloss.Color("#d3d3d3") + white = lipgloss.Color("#ffffff") +) + +var ( + // HeaderStyle is the lipgloss style used for the table headers. + HeaderStyle = lipgloss.NewStyle().Foreground(white).Bold(true).Align(lipgloss.Center) + // CellStyle is the base lipgloss style used for the table rows. + CellStyle = lipgloss.NewStyle().Padding(0, 1).Width(14) + // OddRowStyle is the lipgloss style used for odd-numbered table rows. + OddRowStyle = CellStyle.Foreground(gray) + // EvenRowStyle is the lipgloss style used for even-numbered table rows. + EvenRowStyle = CellStyle.Foreground(lightGray) + // BorderStyle is the lipgloss style used for the table border. + BorderStyle = lipgloss.NewStyle().Foreground(white) +) + +// End Styles - move to common - block + +func LoadTUI() *TUI { + m := &TUI{ + width: 20, + height: 15, + loading: true, + } + return m +} + +// InitialTUI returns the initial state of the model. +func InitialTUI(standalone bool) *TUI { + m := &TUI{ + standalone: standalone, + width: 20, + height: 15, + } + + return m +} + +func (m TUI) Init() tea.Cmd { + // Just return `nil`, which means "no I/O right now, please." + return nil +} + +type DebuggableContainer struct { + Name string + Image string +} + +// Update is called to handle user input and update the model's state. +func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case common.Event: + debuggableContainersCh := make(chan interface{}) + // NOTE -> the names of both the channel map and the channel are misleading + // as more than just the debuggable container information is dumped on it + // at the moment. + debuggableContainersChannelMap := map[string]chan interface{}{ + "debuggableContainers": debuggableContainersCh, + } + // In addition to passing the channel(s) we will use to transport data + // we should pass: + // the outputs we want to subscribe to: State | Info | Error + xc := app.NewExecutionContext( + "tui", + // Quiet -> when set to true, returns on the first line for each + // Execution context method + true, + // output type == "subscription" (better name?) + "data", + debuggableContainersChannelMap, + ) + + cparams := &CommandParams{ + // NOTE -> should not always pass docker here. + Runtime: "docker", + GlobalTUI: true, + // Note -> we should not pass this by default, and instead pass it when a user asks. + ActionListDebuggableContainers: true, + // How to pass the target ref: + // TargetRef: "my-nginx" + } + + gcValue, ok := msg.Data.(*command.GenericParams) + if !ok || gcValue == nil { + return nil, nil + } + + go OnCommand(xc, gcValue, cparams) + + counter := 0 + var counterCeiling int + var debuggableContainers []DebuggableContainer + + doneCh := make(chan struct{}) + go func() { + for debuggableContainersData := range debuggableContainersCh { + channelResponse, ok := debuggableContainersData.(map[string]string) + if !ok || channelResponse == nil { + continue + } + infoValue, infoExists := channelResponse["info"] + if infoExists { + // Set total debuggable container counter ceiling + if infoValue == "debuggable.containers" && counterCeiling == 0 { + countInt, err := strconv.Atoi(channelResponse["count"]) + if err != nil { + continue + } + counterCeiling = countInt + } else if infoValue == "debuggable.container" { + debuggableContainers = append(debuggableContainers, DebuggableContainer{ + Name: channelResponse["name"], + Image: channelResponse["image"], + }) + counter++ + } + } + + if counterCeiling > 0 && counter == counterCeiling { + break + } + } + m.table = generateTable(debuggableContainers) + close(doneCh) + }() + + <-doneCh + return m, nil + case tea.KeyMsg: + switch { + case key.Matches(msg, keys.Global.Quit): + return m, tea.Quit + // NOTE -> We should only support this back navigation, + // if the tui is not standalone + case key.Matches(msg, keys.Global.Back): + return common.TUIsInstance.Home, nil + case key.Matches(msg, keys.Debug.LoadDebuggableContainers): + m.showDebuggableContainers = !m.showDebuggableContainers + return m, nil + + } + } + return m, nil +} + +func generateTable(debuggableContainers []DebuggableContainer) table.Table { + var rows [][]string + for _, container := range debuggableContainers { + rows = append(rows, []string{container.Name, container.Image}) + } + // Note - we will start this as a lipgloss table, but once we add interaction + // it should likely be converted to a bubble tea table. + t := table.New(). + Border(lipgloss.NormalBorder()). + BorderStyle(BorderStyle). + StyleFunc(func(row, col int) lipgloss.Style { + var style lipgloss.Style + + switch { + case row == 0: + return HeaderStyle + case row%2 == 0: + style = EvenRowStyle + default: + style = OddRowStyle + } + + return style + }). + Headers("Name", "Image"). + Rows(rows...) + + return *t +} + +// View returns the view that should be displayed. +func (m TUI) View() string { + var components []string + + // What do you want to do? + // 1. List debuggable containers + // 2. List debug images + // 3. List debug sessions + // 4. Connect to a debug session + // 5. Start a new debug session + + content := "Debug View" + + components = append(components, content) + + if m.showDebuggableContainers { + components = append(components, m.table.String()) + } + + components = append(components, m.help()) + + return lipgloss.JoinVertical(lipgloss.Left, + components..., + ) +} + +func (m TUI) help() string { + if m.standalone { + return common.HelpStyle("• l list debuggable containers • q: quit") + } + return common.HelpStyle("• l list debuggable containers • esc: back • q: quit") +} diff --git a/pkg/app/master/tui/debug/model.go b/pkg/app/master/tui/debug/model.go deleted file mode 100644 index a01c5425..00000000 --- a/pkg/app/master/tui/debug/model.go +++ /dev/null @@ -1,67 +0,0 @@ -package debug - -import ( - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/lipgloss" - "github.com/mintoolkit/mint/pkg/app/master/tui/common" - "github.com/mintoolkit/mint/pkg/app/master/tui/keys" - - tea "github.com/charmbracelet/bubbletea" -) - -// TUI represents the state of the TUI. -type TUI struct { - standalone bool -} - -// InitialTUI returns the initial state of the model. -func InitialTUI(standalone bool) *TUI { - m := &TUI{ - standalone: standalone, - } - - return m -} - -func (m TUI) Init() tea.Cmd { - // Just return `nil`, which means "no I/O right now, please." - return nil -} - -// Update is called to handle user input and update the model's state. -func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, keys.Global.Quit): - return m, tea.Quit - // NOTE -> We should only support this back navigation, - // if the images tui is not standalone - case key.Matches(msg, keys.Global.Back): - return common.TUIsInstance.Home, nil - } - } - return m, nil -} - -// View returns the view that should be displayed. -func (m TUI) View() string { - var components []string - - content := "Debug support coming soon" - - components = append(components, content) - - components = append(components, m.help()) - - return lipgloss.JoinVertical(lipgloss.Left, - components..., - ) -} - -func (m TUI) help() string { - if m.standalone { - return common.HelpStyle("• q: quit") - } - return common.HelpStyle("• esc: back • q: quit") -} From 43808e562e84fb6017c33496c1335db64b8969bc Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sun, 27 Oct 2024 19:37:01 -0400 Subject: [PATCH 08/14] Open debug tui from home tui Signed-off-by: Evan Harris --- pkg/app/master/tui/home/model.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/app/master/tui/home/model.go b/pkg/app/master/tui/home/model.go index 29c54c61..ee898c07 100644 --- a/pkg/app/master/tui/home/model.go +++ b/pkg/app/master/tui/home/model.go @@ -5,9 +5,9 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/mintoolkit/mint/pkg/app/master/command" + "github.com/mintoolkit/mint/pkg/app/master/command/debug" "github.com/mintoolkit/mint/pkg/app/master/command/images" "github.com/mintoolkit/mint/pkg/app/master/tui/common" - "github.com/mintoolkit/mint/pkg/app/master/tui/debug" "github.com/mintoolkit/mint/pkg/app/master/tui/keys" ) @@ -46,9 +46,14 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { common.TUIsInstance.Images = LoadTUI return LoadTUI.Update(getImagesEvent) case key.Matches(msg, keys.Home.Debug): - debugModel := debug.InitialTUI(false) - common.TUIsInstance.Debug = debugModel - return debugModel.Update(nil) + launchDebugEvent := common.Event{ + Type: common.LaunchDebugEvent, + Data: m.Gcvalues, + } + + LoadTUI := debug.LoadTUI() + common.TUIsInstance.Debug = LoadTUI + return LoadTUI.Update(launchDebugEvent) } } return m, nil From 4a796f9126dfa1a24b60e12cd620a23d5eead8ad Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sun, 27 Oct 2024 19:51:18 -0400 Subject: [PATCH 09/14] Rm uused flags Signed-off-by: Evan Harris --- pkg/app/master/command/debug/cli.go | 2 -- pkg/app/master/command/debug/tui.go | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/app/master/command/debug/cli.go b/pkg/app/master/command/debug/cli.go index fd724632..bdd350ca 100644 --- a/pkg/app/master/command/debug/cli.go +++ b/pkg/app/master/command/debug/cli.go @@ -88,8 +88,6 @@ type CommandParams struct { DoFallbackToTargetUser bool // `debug --tui` use mode` TUI bool - // tui -> debug use mode - GlobalTUI bool } func ParseNameValueList(list []string) []NVPair { diff --git a/pkg/app/master/command/debug/tui.go b/pkg/app/master/command/debug/tui.go index 64c21a6e..b65d63d3 100644 --- a/pkg/app/master/command/debug/tui.go +++ b/pkg/app/master/command/debug/tui.go @@ -104,8 +104,7 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cparams := &CommandParams{ // NOTE -> should not always pass docker here. - Runtime: "docker", - GlobalTUI: true, + Runtime: "docker", // Note -> we should not pass this by default, and instead pass it when a user asks. ActionListDebuggableContainers: true, // How to pass the target ref: From 69ec4076e03a99d17ba2aeb6d3fac6810c151dcd Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sun, 27 Oct 2024 19:57:46 -0400 Subject: [PATCH 10/14] Allow debug --tui invocation with no other flags Signed-off-by: Evan Harris --- pkg/app/master/command/debug/cli.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/app/master/command/debug/cli.go b/pkg/app/master/command/debug/cli.go index bdd350ca..33956739 100644 --- a/pkg/app/master/command/debug/cli.go +++ b/pkg/app/master/command/debug/cli.go @@ -284,7 +284,7 @@ var CLI = &cli.Command{ !commandParams.ActionConnectSession && commandParams.TargetRef == "" { if ctx.Args().Len() < 1 { - if commandParams.Runtime != crt.KubernetesRuntime { + if !commandParams.TUI && commandParams.Runtime != crt.KubernetesRuntime { xc.Out.Error("param.target", "missing target") cli.ShowCommandHelp(ctx, Name) return nil From 893dd4d615db52366f1e5d372183cf27f08ec6f8 Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sun, 27 Oct 2024 20:46:57 -0400 Subject: [PATCH 11/14] Support use of debug --tui to render debuggable containers Signed-off-by: Evan Harris --- pkg/app/master/command/debug/cli.go | 12 ++++++++- pkg/app/master/command/debug/handler.go | 6 ----- pkg/app/master/command/debug/tui.go | 35 +++++++++++++++++++++---- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/pkg/app/master/command/debug/cli.go b/pkg/app/master/command/debug/cli.go index 33956739..45cbb707 100644 --- a/pkg/app/master/command/debug/cli.go +++ b/pkg/app/master/command/debug/cli.go @@ -8,6 +8,7 @@ import ( "github.com/mintoolkit/mint/pkg/app" "github.com/mintoolkit/mint/pkg/app/master/command" + "github.com/mintoolkit/mint/pkg/app/master/tui" "github.com/mintoolkit/mint/pkg/crt" ) @@ -172,6 +173,15 @@ var CLI = &cli.Command{ }, Action: func(ctx *cli.Context) error { gcvalues := command.GlobalFlagValues(ctx) + + // If we stick with this approach, the user should be communicated to + // use `--tui` as a standalone flag for `debug` + if ctx.Bool(command.FlagTUI) { + initialTUI := InitialTUI(true, gcvalues) + tui.RunTUI(initialTUI, true) + return nil + } + xc := app.NewExecutionContext( Name, gcvalues.QuietCLIMode, @@ -284,7 +294,7 @@ var CLI = &cli.Command{ !commandParams.ActionConnectSession && commandParams.TargetRef == "" { if ctx.Args().Len() < 1 { - if !commandParams.TUI && commandParams.Runtime != crt.KubernetesRuntime { + if commandParams.Runtime != crt.KubernetesRuntime { xc.Out.Error("param.target", "missing target") cli.ShowCommandHelp(ctx, Name) return nil diff --git a/pkg/app/master/command/debug/handler.go b/pkg/app/master/command/debug/handler.go index 73f289af..201b8e7f 100644 --- a/pkg/app/master/command/debug/handler.go +++ b/pkg/app/master/command/debug/handler.go @@ -7,7 +7,6 @@ import ( "github.com/mintoolkit/mint/pkg/app" "github.com/mintoolkit/mint/pkg/app/master/command" - "github.com/mintoolkit/mint/pkg/app/master/tui" "github.com/mintoolkit/mint/pkg/app/master/version" cmd "github.com/mintoolkit/mint/pkg/command" "github.com/mintoolkit/mint/pkg/crt" @@ -84,11 +83,6 @@ func OnCommand( resolved := command.ResolveAutoRuntime(commandParams.Runtime) logger.Tracef("runtime.handler: rt=%s resolved=%s", commandParams.Runtime, resolved) - if commandParams.TUI { // `debug --tui`` - initialTUI := InitialTUI(true) - tui.RunTUI(initialTUI, true) - } - switch resolved { case crt.DockerRuntime: client, err := dockerclient.New(gparams.ClientConfig) diff --git a/pkg/app/master/command/debug/tui.go b/pkg/app/master/command/debug/tui.go index b65d63d3..ffd65d51 100644 --- a/pkg/app/master/command/debug/tui.go +++ b/pkg/app/master/command/debug/tui.go @@ -24,6 +24,8 @@ type TUI struct { table table.Table showDebuggableContainers bool + + gcvalues *command.GenericParams } // Styles - move to `common` @@ -58,11 +60,12 @@ func LoadTUI() *TUI { } // InitialTUI returns the initial state of the model. -func InitialTUI(standalone bool) *TUI { +func InitialTUI(standalone bool, gcvalues *command.GenericParams) *TUI { m := &TUI{ standalone: standalone, width: 20, height: 15, + gcvalues: gcvalues, } return m @@ -156,6 +159,7 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { }() <-doneCh + m.showDebuggableContainers = !m.showDebuggableContainers return m, nil case tea.KeyMsg: switch { @@ -166,6 +170,17 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, keys.Global.Back): return common.TUIsInstance.Home, nil case key.Matches(msg, keys.Debug.LoadDebuggableContainers): + // Kickoff loading of debuggable containers in standable. + if m.standalone { + loadDebuggableContainers := common.Event{ + Type: common.LaunchDebugEvent, + Data: m.gcvalues, + } + m, _ := m.Update(loadDebuggableContainers) + return m, nil + } + + // When used via `tui -> debug` m.showDebuggableContainers = !m.showDebuggableContainers return m, nil @@ -215,12 +230,13 @@ func (m TUI) View() string { // 4. Connect to a debug session // 5. Start a new debug session - content := "Debug View" + content := "Debug Dashboard\n" components = append(components, content) if m.showDebuggableContainers { - components = append(components, m.table.String()) + header := "Debuggable Containers\n" + components = append(components, header, m.table.String()) } components = append(components, m.help()) @@ -231,8 +247,17 @@ func (m TUI) View() string { } func (m TUI) help() string { + var listOrHide string + + if m.showDebuggableContainers { + listOrHide = "hide" + } else { + listOrHide = "list" + } + if m.standalone { - return common.HelpStyle("• l list debuggable containers • q: quit") + return common.HelpStyle("• l: " + listOrHide + " debuggable containers • q: quit") } - return common.HelpStyle("• l list debuggable containers • esc: back • q: quit") + + return common.HelpStyle("• l: " + listOrHide + " debuggable containers • esc: back • q: quit") } From 02b1a055fb1a369a9a40a12675e23ebd36486518 Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sun, 27 Oct 2024 20:48:02 -0400 Subject: [PATCH 12/14] Fix spelling Signed-off-by: Evan Harris --- pkg/app/execontext.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/app/execontext.go b/pkg/app/execontext.go index 03f83aa5..17c4c4a6 100644 --- a/pkg/app/execontext.go +++ b/pkg/app/execontext.go @@ -140,7 +140,7 @@ func NewOutput(cmdName string, quiet bool, outputFormat string, channels map[str } // We want to listen to the internal channel for any data - // And dump it onto the appropraite DataChannels + // And dump it onto the appropriate DataChannels go func() { for data := range ref.internalDataCh { if data != nil { From cd6600a03cd76bdf7218a9fc1a2f4868f2f3d933 Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sun, 27 Oct 2024 20:52:29 -0400 Subject: [PATCH 13/14] Change name from data -> subscription Signed-off-by: Evan Harris --- pkg/app/execontext.go | 10 +++++----- pkg/app/master/command/debug/tui.go | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/app/execontext.go b/pkg/app/execontext.go index 17c4c4a6..b390daa9 100644 --- a/pkg/app/execontext.go +++ b/pkg/app/execontext.go @@ -16,9 +16,9 @@ import ( ) const ( - ofJSON = "json" - ofText = "text" - ofData = "data" + ofJSON = "json" + ofText = "text" + ofSubscription = "subscription" ) type ExecutionContext struct { @@ -369,7 +369,7 @@ var ( func (ref *Output) Info(infoType string, params ...OutVars) { // TODO - carry this pattern to other Output methods - if ref.Quiet && ref.OutputFormat != ofData { + if ref.Quiet && ref.OutputFormat != ofSubscription { return } @@ -404,7 +404,7 @@ func (ref *Output) Info(infoType string, params ...OutVars) { fmt.Println(string(jsonData)) case ofText: fmt.Printf("cmd=%s info=%s%s%s\n", ref.CmdName, itcolor(infoType), sep, data) - case ofData: + case ofSubscription: ref.internalDataCh <- msg // Send data to the internal channel default: log.Fatalf("Unknown console output flag: %s\n. It should be either 'text' or 'json", ref.OutputFormat) diff --git a/pkg/app/master/command/debug/tui.go b/pkg/app/master/command/debug/tui.go index ffd65d51..18f5c122 100644 --- a/pkg/app/master/command/debug/tui.go +++ b/pkg/app/master/command/debug/tui.go @@ -100,8 +100,7 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Quiet -> when set to true, returns on the first line for each // Execution context method true, - // output type == "subscription" (better name?) - "data", + "subscription", debuggableContainersChannelMap, ) From 814610f509a4b78310d738ad65cf8f122042de04 Mon Sep 17 00:00:00 2001 From: Evan Harris Date: Sun, 27 Oct 2024 20:57:20 -0400 Subject: [PATCH 14/14] Improve comment and fix typo Signed-off-by: Evan Harris --- pkg/app/master/command/debug/tui.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/app/master/command/debug/tui.go b/pkg/app/master/command/debug/tui.go index 18f5c122..09dc93d5 100644 --- a/pkg/app/master/command/debug/tui.go +++ b/pkg/app/master/command/debug/tui.go @@ -165,11 +165,11 @@ func (m TUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, keys.Global.Quit): return m, tea.Quit // NOTE -> We should only support this back navigation, - // if the tui is not standalone + // if the tui is not in standalone mode. case key.Matches(msg, keys.Global.Back): return common.TUIsInstance.Home, nil case key.Matches(msg, keys.Debug.LoadDebuggableContainers): - // Kickoff loading of debuggable containers in standable. + // Kickoff loading of debuggable containers in standalone mode. if m.standalone { loadDebuggableContainers := common.Event{ Type: common.LaunchDebugEvent,