From f67f549618ac52c09435d5a7d79bdecab2011e61 Mon Sep 17 00:00:00 2001 From: Matthew Coleman Date: Sun, 25 Feb 2024 13:01:33 -0500 Subject: [PATCH] Full pane toggling support (#43) --- README.md | 1 + internal/lib/display.go | 65 +++++++++----- internal/lib/display_termdash.go | 149 +++++++++++++++++++++++-------- internal/lib/display_tview.go | 18 ++-- internal/lib/results.go | 8 +- main.go | 8 +- 6 files changed, 180 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index a653d5f..5d525b4 100644 --- a/README.md +++ b/README.md @@ -146,5 +146,6 @@ There doesn't seem to be much out there easily visible that matches the same set but there are a few projects I've found that do some things similarly. - [DataDash](https://github.com/keithknott26/datadash), a data visualization tool for the terminal. +- [Grafterm](https://github.com/slok/grafterm), visualize metrics dashboards on the terminal. - [Euoporie](https://github.com/joouha/euporie), a terminal interactive computing environment for Jupyter. diff --git a/internal/lib/display.go b/internal/lib/display.go index e32c6ab..b63e1bf 100644 --- a/internal/lib/display.go +++ b/internal/lib/display.go @@ -11,7 +11,6 @@ import ( "github.com/mum4k/termdash/cell" "github.com/mum4k/termdash/widgets/sparkline" "github.com/mum4k/termdash/widgets/text" - "golang.org/x/exp/slog" ) // Display driver constants. Each display mode uses a specific display driver. @@ -110,7 +109,7 @@ func RawDisplay(query string) { } // Update the results pane with new results as they are generated. -func StreamDisplay(query string) { +func StreamDisplay(query string, showHelp, showLogs bool) { var ( reader = readerIndexes[query] // Reader index for the query. ) @@ -120,7 +119,15 @@ func StreamDisplay(query string) { reader.Dec() // Initialize the display. - resultsView, _, _ := initDisplayTviewText(helpText()) + resultsView, helpView, logsView, flexBox := initDisplayTviewText(helpText()) + + // Hide displays we don't want to show. + if !showHelp { + flexBox.RemoveItem(helpView) + } + if !showLogs { + flexBox.RemoveItem(logsView) + } // Start the display. display( @@ -156,7 +163,7 @@ func StreamDisplay(query string) { } // Creates a table of results for the results pane. -func TableDisplay(query string, filters []string) { +func TableDisplay(query string, filters []string, showHelp, showLogs bool) { var ( reader = readerIndexes[query] // Reader index for the query. tableCellPadding = strings.Repeat(" ", TABLE_PADDING) // Padding to add to table cell content. @@ -168,7 +175,15 @@ func TableDisplay(query string, filters []string) { reader.Dec() // Initialize the display. - resultsView, _, _ := initDisplayTviewTable(helpText()) + resultsView, helpView, logsView, flexBox := initDisplayTviewTable(helpText()) + + // Hide displays we don't want to show. + if !showHelp { + flexBox.RemoveItem(helpView) + } + if !showLogs { + flexBox.RemoveItem(logsView) + } // Start the display. display( @@ -258,8 +273,11 @@ func TableDisplay(query string, filters []string) { } // Creates a graph of results for the results pane. -func GraphDisplay(query string, filters []string) { +func GraphDisplay(query string, filters []string, showHelp, showLogs bool) { var ( + helpWidget, logsWidget *text.Text // Accessory widgets to display. + resultsWidget *sparkline.SparkLine // Results widget. + reader = readerIndexes[query] // Reader index for the query. valueIndex = 0 // Index of the result value to graph. ) @@ -274,25 +292,24 @@ func GraphDisplay(query string, filters []string) { } // Initialize the results view. - resultWidget, err := sparkline.New( + resultsWidget, err := sparkline.New( sparkline.Label(store.GetLabels(query)[valueIndex]), sparkline.Color(cell.ColorGreen), ) e(err) // Initialize the help view. - helpWidget, err := text.New() - e(err) - helpWidget.Write(helpText()) + if showHelp { + helpWidget, err = text.New() + e(err) + helpWidget.Write(helpText()) + } // Initialize the logs view. - logsWidget, err := text.New() - e(err) - logsWidgetWriter := termdashTextWriter{text: *logsWidget} - slog.SetDefault(slog.New(slog.NewTextHandler( - &logsWidgetWriter, - &slog.HandlerOptions{Level: config.SlogLogLevel()}, - ))) + if showLogs { + logsWidget, err = text.New() + e(err) + } // Start the display. display( @@ -305,9 +322,9 @@ func GraphDisplay(query string, filters []string) { switch value.(type) { case int64: - resultWidget.Add([]int{int(value.(int64))}) + resultsWidget.Add([]int{int(value.(int64))}) case float64: - resultWidget.Add([]int{int(value.(float64))}) + resultsWidget.Add([]int{int(value.(float64))}) } } @@ -326,9 +343,9 @@ func GraphDisplay(query string, filters []string) { switch value.(type) { case int64: - resultWidget.Add([]int{int(value.(int64))}) + resultsWidget.Add([]int{int(value.(int64))}) case float64: - resultWidget.Add([]int{int(value.(float64))}) + resultsWidget.Add([]int{int(value.(float64))}) } } } @@ -337,5 +354,9 @@ func GraphDisplay(query string, filters []string) { // Initialize the display. This must happen after the display function is invoked, otherwise data // will never appear. - initDisplayTermdash(resultWidget, helpWidget, &logsWidgetWriter.text) + initDisplayTermdash(termDashWidgets{ + resultsWidget: resultsWidget, + helpWidget: helpWidget, + logsWidget: logsWidget, + }) } diff --git a/internal/lib/display_termdash.go b/internal/lib/display_termdash.go index b431337..3537e1e 100644 --- a/internal/lib/display_termdash.go +++ b/internal/lib/display_termdash.go @@ -28,6 +28,12 @@ func (t *termdashTextWriter) Write(p []byte) (n int, err error) { return len(p), nil } +// Used to supply optional widgets to Termdash initialization. +type termDashWidgets struct { + helpWidget, logsWidget *text.Text // Accessory widgets which are always text. + resultsWidget widgetapi.Widget // Results widget, which varies by display type. +} + var ( appTermdash *tcell.Terminal // Termdash display. cancel context.CancelFunc // Cancel function for the termdash display. @@ -78,10 +84,13 @@ func errorTermdashHandler(e error) { } // Sets-up the termdash container, which defines the overall layout, and begins running the display. -func initDisplayTermdash(resultsWidget, helpWidget, logsWidget widgetapi.Widget) { +// func initDisplayTermdash(resultsWidget, helpWidget, logsWidget widgetapi.Widget) { +func initDisplayTermdash(widgets termDashWidgets) { var ( - ctx context.Context // Termdash specific context. - err error // General error holder. + ctx context.Context // Termdash specific context. + err error // General error holder. + logsWidgetWriter termdashTextWriter // Writer implementation for logs. + widgetContainer *container.Container // Wrapper for widgets. ) // Set-up the context and enable it to close on key-press. @@ -91,47 +100,117 @@ func initDisplayTermdash(resultsWidget, helpWidget, logsWidget widgetapi.Widget) appTermdash, err = tcell.New() e(err) - // Render the widget. - c, err := container.New( - appTermdash, - container.PaddingBottom(OUTER_PADDING_BOTTOM), - container.PaddingLeft(OUTER_PADDING_LEFT), - container.PaddingTop(OUTER_PADDING_TOP), - container.PaddingRight(OUTER_PADDING_RIGHT), - container.SplitHorizontal( - container.Top( - container.Border(linestyle.Light), - container.BorderTitle("Results"), - container.BorderTitleAlignCenter(), - container.PlaceWidget(resultsWidget), - ), - container.Bottom( - container.SplitHorizontal( - container.Top( - container.Border(linestyle.Light), - container.BorderTitle("Help"), - container.BorderTitleAlignCenter(), - container.PlaceWidget(helpWidget), - ), - container.Bottom( - container.Border(linestyle.Light), - container.BorderTitle("Logs"), - container.BorderTitleAlignCenter(), - container.PlaceWidget(logsWidget), + if widgets.helpWidget != nil && widgets.logsWidget != nil { + // All widgets enabled. + widgetContainer, err = container.New( + appTermdash, + container.PaddingBottom(OUTER_PADDING_BOTTOM), + container.PaddingLeft(OUTER_PADDING_LEFT), + container.PaddingTop(OUTER_PADDING_TOP), + container.PaddingRight(OUTER_PADDING_RIGHT), + container.SplitHorizontal( + container.Top( + container.Border(linestyle.Light), + container.BorderTitle("Results"), + container.BorderTitleAlignCenter(), + container.PlaceWidget(widgets.resultsWidget), + ), + container.Bottom( + container.SplitHorizontal( + container.Top( + container.Border(linestyle.Light), + container.BorderTitle("Help"), + container.BorderTitleAlignCenter(), + container.PlaceWidget(widgets.helpWidget), + ), + container.Bottom( + container.Border(linestyle.Light), + container.BorderTitle("Logs"), + container.BorderTitleAlignCenter(), + container.PlaceWidget(widgets.logsWidget), + ), + container.SplitOption(container.SplitPercent(RelativePerc(RESULTS_SIZE, HELP_SIZE))), ), - container.SplitOption(container.SplitPercent(RelativePerc(RESULTS_SIZE, HELP_SIZE))), ), + container.SplitOption(container.SplitPercent(RESULTS_SIZE)), ), - container.SplitOption(container.SplitPercent(RESULTS_SIZE)), - ), - ) + ) + } else if widgets.helpWidget != nil { + // We have just the help widget enabled. + widgetContainer, err = container.New( + appTermdash, + container.PaddingBottom(OUTER_PADDING_BOTTOM), + container.PaddingLeft(OUTER_PADDING_LEFT), + container.PaddingTop(OUTER_PADDING_TOP), + container.PaddingRight(OUTER_PADDING_RIGHT), + container.SplitHorizontal( + container.Top( + container.Border(linestyle.Light), + container.BorderTitle("Results"), + container.BorderTitleAlignCenter(), + container.PlaceWidget(widgets.resultsWidget), + ), + container.Bottom( + container.Border(linestyle.Light), + container.BorderTitle("Help"), + container.BorderTitleAlignCenter(), + container.PlaceWidget(widgets.helpWidget), + ), + container.SplitOption(container.SplitPercent(RESULTS_SIZE+LOGS_SIZE)), + ), + ) + } else if widgets.logsWidget != nil { + // We have just the logs widget enabled. We also need to point logs to it. + logsWidgetWriter = termdashTextWriter{text: *widgets.logsWidget} + slog.SetDefault(slog.New(slog.NewTextHandler( + &logsWidgetWriter, + &slog.HandlerOptions{Level: config.SlogLogLevel()}, + ))) + + widgetContainer, err = container.New( + appTermdash, + container.PaddingBottom(OUTER_PADDING_BOTTOM), + container.PaddingLeft(OUTER_PADDING_LEFT), + container.PaddingTop(OUTER_PADDING_TOP), + container.PaddingRight(OUTER_PADDING_RIGHT), + container.SplitHorizontal( + container.Top( + container.Border(linestyle.Light), + container.BorderTitle("Results"), + container.BorderTitleAlignCenter(), + container.PlaceWidget(widgets.resultsWidget), + ), + container.Bottom( + container.Border(linestyle.Light), + container.BorderTitle("Logs"), + container.BorderTitleAlignCenter(), + container.PlaceWidget(&logsWidgetWriter.text), + ), + container.SplitOption(container.SplitPercent(RESULTS_SIZE+HELP_SIZE)), + ), + ) + } else { + // Just the results pane. + widgetContainer, err = container.New( + appTermdash, + container.PaddingBottom(OUTER_PADDING_BOTTOM), + container.PaddingLeft(OUTER_PADDING_LEFT), + container.PaddingTop(OUTER_PADDING_TOP), + container.PaddingRight(OUTER_PADDING_RIGHT), + container.Border(linestyle.Light), + container.BorderTitle("Results"), + container.BorderTitleAlignCenter(), + container.PlaceWidget(widgets.resultsWidget), + ) + } + e(err) // Run the display. termdash.Run( ctx, appTermdash, - c, + widgetContainer, termdash.ErrorHandler(errorTermdashHandler), termdash.KeyboardSubscriber(keyboardTermdashHandler), ) diff --git a/internal/lib/display_tview.go b/internal/lib/display_tview.go index d2399ac..7bac472 100644 --- a/internal/lib/display_tview.go +++ b/internal/lib/display_tview.go @@ -67,6 +67,7 @@ func keyboardTviewHandler(event *tcell.EventKey) *tcell.EventKey { func initDisplayTviewTable(helpText string) ( resultsView *tview.Table, helpView, logsView *tview.TextView, + flexBox *tview.Flex, ) { resultsView = tview.NewTable() helpView = tview.NewTextView() @@ -76,13 +77,16 @@ func initDisplayTviewTable(helpText string) ( resultsView.SetBorders(true) resultsView.SetBorder(true).SetTitle("Results") - initDisplayTview(resultsView, helpView, logsView, helpText) + flexBox = initDisplayTview(resultsView, helpView, logsView, helpText) return } // Display init function specific to text results. -func initDisplayTviewText(helpText string) (resultsView, helpView, logsView *tview.TextView) { +func initDisplayTviewText(helpText string) ( + resultsView, helpView, logsView *tview.TextView, + flexBox *tview.Flex, +) { resultsView = tview.NewTextView() helpView = tview.NewTextView() logsView = tview.NewTextView() @@ -91,7 +95,7 @@ func initDisplayTviewText(helpText string) (resultsView, helpView, logsView *tvi resultsView.SetChangedFunc(func() { appTview.Draw() }) resultsView.SetBorder(true).SetTitle("Results") - initDisplayTview(resultsView, helpView, logsView, helpText) + flexBox = initDisplayTview(resultsView, helpView, logsView, helpText) return } @@ -106,10 +110,8 @@ func initDisplayTview( resultsView tview.Primitive, helpView, logsView *tview.TextView, helpText string, -) { - var ( - flexBox = tview.NewFlex() // Tview flexbox. - ) +) (flexBox *tview.Flex) { + flexBox = tview.NewFlex() // Tview flexbox. // Set-up the layout and apply views. flexBox = flexBox.SetDirection(tview.FlexRow). @@ -136,4 +138,6 @@ func initDisplayTview( logsView, &slog.HandlerOptions{Level: config.SlogLogLevel()}, ))) + + return } diff --git a/internal/lib/results.go b/internal/lib/results.go index 59ce382..4000929 100644 --- a/internal/lib/results.go +++ b/internal/lib/results.go @@ -142,7 +142,7 @@ func Results( ctx context.Context, displayMode DisplayMode, query string, - history bool, + history, showHelp, showLogs bool, inputConfig Config, inputPauseQueryChans map[string]chan bool, resultsReadyChan chan bool, @@ -204,13 +204,13 @@ func Results( RawDisplay(query) case DISPLAY_MODE_STREAM: driver = DISPLAY_TVIEW - StreamDisplay(query) + StreamDisplay(query, showHelp, showLogs) case DISPLAY_MODE_TABLE: driver = DISPLAY_TVIEW - TableDisplay(query, filters) + TableDisplay(query, filters, showHelp, showLogs) case DISPLAY_MODE_GRAPH: driver = DISPLAY_TERMDASH - GraphDisplay(query, filters) + GraphDisplay(query, filters, showHelp, showLogs) default: slog.Error(fmt.Sprintf("Invalid result driver: %d\n", displayMode)) os.Exit(1) diff --git a/main.go b/main.go index d20e8f4..f80ee2b 100644 --- a/main.go +++ b/main.go @@ -54,14 +54,16 @@ var ( displayMode int // Result mode to display. filters string // Result filters. history bool // Whether or not to preserve or use historical results. + labels string // Result value labels. logLevel string // Log level. mode int // Mode to execute in. port string // Port for RPC. promExporterAddr string // Address for Prometheus metrics page. promPushgatewayAddr string // Address for Prometheus Pushgateway. queries queriesArg // Queries to execute. + showHelp bool // Whether or not to show help. + showLogs bool // Whether or not to show logs. silent bool // Whether or not to be quiet. - labels string // Result value labels. ctx = context.Background() // Initialize context. logger = log.Default() // Logging system. @@ -93,6 +95,8 @@ func main() { // Define arguments. flag.BoolVar(&history, "history", true, "Whether or not to use or preserve history.") + flag.BoolVar(&showHelp, "show-help", true, "Whether or not to show help displays.") + flag.BoolVar(&showLogs, "show-logs", false, "Whether or not to show log displays.") flag.BoolVar(&silent, "silent", false, "Don't output anything to a console.") flag.IntVar(&count, "count", 1, "Number of query executions. -1 for continuous.") flag.IntVar(&delay, "delay", 3, "Delay between queries (seconds).") @@ -175,6 +179,8 @@ func main() { lib.DisplayMode(displayMode), ctx.Value("queries").([]string)[0], // Always start with the first query. history, + showHelp, + showLogs, lib.Config{ LogLevel: logLevel, PrometheusExporterAddr: promExporterAddr,