Skip to content

Commit

Permalink
preflight ui
Browse files Browse the repository at this point in the history
  • Loading branch information
marccampbell committed Jul 19, 2019
1 parent fe8f2be commit a96e172
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 46 deletions.
254 changes: 212 additions & 42 deletions cmd/preflight/cli/interactive_results.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,103 +2,273 @@ package cli

import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"time"

ui "github.com/gizak/termui/v3"
"github.com/gizak/termui/v3/widgets"
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
)

type nodeValue string

func (nv nodeValue) String() string {
return string(nv)
}
var (
selectedResult = 0
isShowingSaved = false
)

func showInteractiveResults(analyzeResults []*analyzerunner.AnalyzeResult) error {
func showInteractiveResults(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
if err := ui.Init(); err != nil {
return err
}
defer ui.Close()

selectedResult := 0

preflightTable := getPreflightTable(analyzeResults)
details := getDetails(analyzeResults[selectedResult])

grid := ui.NewGrid()
termWidth, termHeight := ui.TerminalDimensions()
grid.SetRect(0, 0, termWidth, termHeight)

grid.Set(
ui.NewRow(1.0,
ui.NewCol(1.0/2, preflightTable),
ui.NewCol(1.0/2, details),
),
)

ui.Render(grid)
drawUI(preflightName, analyzeResults)

uiEvents := ui.PollEvents()
for {
select {
case e := <-uiEvents:
switch e.ID {
case "q", "<C-c>":
case "<C-c>":
return nil
case "q":
if isShowingSaved == true {
isShowingSaved = false
ui.Clear()
drawUI(preflightName, analyzeResults)
} else {
return nil
}
case "s":
filename, err := save(preflightName, analyzeResults)
if err != nil {
// show
} else {
showSaved(filename)
go func() {
time.Sleep(time.Second * 5)
isShowingSaved = false
ui.Clear()
drawUI(preflightName, analyzeResults)
}()
}
case "<Resize>":
payload := e.Payload.(ui.Resize)
grid.SetRect(0, 0, payload.Width, payload.Height)
ui.Clear()
ui.Render(grid)
drawUI(preflightName, analyzeResults)
case "<Down>":
if selectedResult < len(analyzeResults)-1 {
selectedResult++
} else {
selectedResult = 0
}
ui.Clear()
drawUI(preflightName, analyzeResults)
case "<Up>":
if selectedResult > 0 {
selectedResult--
} else {
selectedResult = len(analyzeResults) - 1
}
ui.Clear()
drawUI(preflightName, analyzeResults)
}
}
}
}

func getPreflightTable(analyzeResults []*analyzerunner.AnalyzeResult) *widgets.Table {
func drawUI(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) {
drawGrid(analyzeResults)
drawHeader(preflightName)
drawFooter()
}

func drawGrid(analyzeResults []*analyzerunner.AnalyzeResult) {
drawPreflightTable(analyzeResults)
drawDetails(analyzeResults[selectedResult])
}

func drawHeader(preflightName string) {
termWidth, _ := ui.TerminalDimensions()

title := widgets.NewParagraph()
title.Text = fmt.Sprintf("%s Preflight Checks", strings.Title(strings.Replace(preflightName, "-", " ", -1)))
title.TextStyle.Fg = ui.ColorWhite
title.TextStyle.Bg = ui.ColorClear
title.TextStyle.Modifier = ui.ModifierBold
title.Border = false

left := termWidth/2 - 2*len(title.Text)/3
right := termWidth/2 + (termWidth/2 - left)

title.SetRect(left, 0, right, 1)
ui.Render(title)
}

func drawFooter() {
termWidth, termHeight := ui.TerminalDimensions()

instructions := widgets.NewParagraph()
instructions.Text = "[q] quit [s] save [↑][↓] scroll"
instructions.Border = false

left := 0
right := termWidth
top := termHeight - 1
bottom := termHeight

instructions.SetRect(left, top, right, bottom)
ui.Render(instructions)
}

func drawPreflightTable(analyzeResults []*analyzerunner.AnalyzeResult) {
termWidth, termHeight := ui.TerminalDimensions()

table := widgets.NewTable()
table.SetRect(0, 3, termWidth/2, termHeight-6)
table.FillRow = true
table.Border = true
table.Rows = [][]string{}
table.ColumnWidths = []int{termWidth}

for i, analyzeResult := range analyzeResults {
title := analyzeResult.Title
if analyzeResult.IsPass {
title = fmt.Sprintf("✔ %s", title)
} else if analyzeResult.IsWarn {
title = fmt.Sprintf("⚠️ %s", title)
} else if analyzeResult.IsFail {
title = fmt.Sprintf("✘ %s", title)
}
table.Rows = append(table.Rows, []string{
analyzeResult.Title,
title,
})

if analyzeResult.IsPass {
table.RowStyles[i] = ui.NewStyle(ui.ColorGreen, ui.ColorClear, ui.ModifierBold)
if i == selectedResult {
table.RowStyles[i] = ui.NewStyle(ui.ColorGreen, ui.ColorClear, ui.ModifierReverse)
} else {
table.RowStyles[i] = ui.NewStyle(ui.ColorGreen, ui.ColorClear)
}
} else if analyzeResult.IsWarn {
table.RowStyles[i] = ui.NewStyle(ui.ColorYellow, ui.ColorClear, ui.ModifierBold)
if i == selectedResult {
table.RowStyles[i] = ui.NewStyle(ui.ColorYellow, ui.ColorClear, ui.ModifierReverse)
} else {
table.RowStyles[i] = ui.NewStyle(ui.ColorYellow, ui.ColorClear)
}
} else if analyzeResult.IsFail {
table.RowStyles[i] = ui.NewStyle(ui.ColorRed, ui.ColorClear)
if i == selectedResult {
table.RowStyles[i] = ui.NewStyle(ui.ColorRed, ui.ColorClear, ui.ModifierReverse)
} else {
table.RowStyles[i] = ui.NewStyle(ui.ColorRed, ui.ColorClear)
}
}
}

return table
ui.Render(table)
}

func getDetails(analysisResult *analyzerunner.AnalyzeResult) *ui.Grid {
grid := ui.NewGrid()

entries := []interface{}{}
func drawDetails(analysisResult *analyzerunner.AnalyzeResult) {
termWidth, _ := ui.TerminalDimensions()

currentTop := 4
title := widgets.NewParagraph()
title.Text = analysisResult.Title
title.Border = false
entries = append(entries, ui.NewRow(0.2, ui.NewCol(1.0, title)))
if analysisResult.IsPass {
title.TextStyle = ui.NewStyle(ui.ColorGreen, ui.ColorClear, ui.ModifierBold)
} else if analysisResult.IsWarn {
title.TextStyle = ui.NewStyle(ui.ColorYellow, ui.ColorClear, ui.ModifierBold)
} else if analysisResult.IsFail {
title.TextStyle = ui.NewStyle(ui.ColorRed, ui.ColorClear, ui.ModifierBold)
}
height := estimateNumberOfLines(title.Text, termWidth/2)
title.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
ui.Render(title)
currentTop = currentTop + height + 1

message := widgets.NewParagraph()
message.Text = analysisResult.Message
message.Border = false
entries = append(entries, ui.NewRow(0.2, ui.NewCol(1.0, message)))
height = estimateNumberOfLines(message.Text, termWidth/2) + 2
message.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
ui.Render(message)
currentTop = currentTop + height + 1

if analysisResult.URI != "" {
uri := widgets.NewParagraph()
uri.Text = fmt.Sprintf("For more information: %s", analysisResult.URI)
uri.Border = false
entries = append(entries, ui.NewRow(0.2, ui.NewCol(1.0, uri)))
height = estimateNumberOfLines(uri.Text, termWidth/2)
uri.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
ui.Render(uri)
currentTop = currentTop + height + 1
}
}

func estimateNumberOfLines(text string, width int) int {
if len(text) < width {
return 1
}

lines := len(text)/width + 1
return lines
}

func save(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) (string, error) {
filename := path.Join(homeDir(), fmt.Sprintf("%s-results.txt", preflightName))
_, err := os.Stat(filename)
if err == nil {
os.Remove(filename)
}

results := fmt.Sprintf("%s Preflight Checks\n\n", strings.Title(strings.Replace(preflightName, "-", " ", -1)))
for _, analyzeResult := range analyzeResults {
result := ""

if analyzeResult.IsPass {
result = "Check PASS\n"
} else if analyzeResult.IsWarn {
result = "Check WARN\n"
} else if analyzeResult.IsFail {
result = "Check FAIL\n"
}

result = result + fmt.Sprintf("Title: %s\n", analyzeResult.Title)
result = result + fmt.Sprintf("Message: %s\n", analyzeResult.Message)

if analyzeResult.URI != "" {
result = result + fmt.Sprintf("URI: %s\n", analyzeResult.URI)
}

result = result + "\n------------\n"

results = results + result
}

if err := ioutil.WriteFile(filename, []byte(results), 0644); err != nil {
return "", err
}

return filename, nil
}

func showSaved(filename string) {
termWidth, termHeight := ui.TerminalDimensions()

savedMessage := widgets.NewParagraph()
savedMessage.Text = fmt.Sprintf("Preflight results saved to\n\n%s", filename)
savedMessage.WrapText = true
savedMessage.Border = true

left := termWidth/2 - 20
right := termWidth/2 + 20
top := termHeight/2 - 4
bottom := termHeight/2 + 4

savedMessage.SetRect(left, top, right, bottom)
ui.Render(savedMessage)

grid.Set(entries...)
return grid
isShowingSaved = true
}
2 changes: 1 addition & 1 deletion cmd/preflight/cli/run_nocrd.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func runPreflightsNoCRD(v *viper.Viper, arg string) error {
}

if v.GetBool("interactive") {
return showInteractiveResults(analyzeResults)
return showInteractiveResults(preflight.Name, analyzeResults)
}

fmt.Printf("only interactive results are supported\n")
Expand Down
6 changes: 3 additions & 3 deletions config/samples/troubleshoot_v1beta1_preflight.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
apiVersion: troubleshoot.replicated.com/v1beta1
kind: Preflight
metadata:
name: preflight-sample
name: sample-app
spec:
analyzers:
- clusterVersion:
outcomes:
- fail:
when: "< 1.14.0"
message: You need more kubernetes
message: Sorry, this application requires at least Kubernetes 1.14.0. Please update your Kubernetes cluster before installing.
uri: https://help.replicated.com/kubernetes-version
- warn:
when: "< 1.15.0"
Expand All @@ -23,7 +23,7 @@ spec:
- fail:
message: The micr0k8s storage class thing was not found
- pass:
message: All good on storage classes
message: The required storage class was found in the cluster.
- secret:
checkName: PG URI
secretName: postgres
Expand Down

0 comments on commit a96e172

Please sign in to comment.