Skip to content

Commit d98172e

Browse files
authored
Merge pull request #215 from replicatedhq/analyzer-cli
Interactive results for support bundle kinds
2 parents 890cbb1 + b070b40 commit d98172e

File tree

2 files changed

+223
-7
lines changed

2 files changed

+223
-7
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
"path"
8+
"time"
9+
10+
ui "github.com/gizak/termui/v3"
11+
"github.com/gizak/termui/v3/widgets"
12+
"github.com/pkg/errors"
13+
"github.com/replicatedhq/troubleshoot/cmd/util"
14+
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
15+
)
16+
17+
var (
18+
selectedResult = 0
19+
isShowingSaved = false
20+
)
21+
22+
func showInteractiveResults(analyzeResults []*analyzerunner.AnalyzeResult) error {
23+
if err := ui.Init(); err != nil {
24+
return errors.Wrap(err, "failed to create terminal ui")
25+
}
26+
defer ui.Close()
27+
drawUI(analyzeResults)
28+
29+
uiEvents := ui.PollEvents()
30+
for {
31+
select {
32+
case e := <-uiEvents:
33+
switch e.ID {
34+
case "<C-c>":
35+
return nil
36+
case "q":
37+
if isShowingSaved == true {
38+
isShowingSaved = false
39+
ui.Clear()
40+
drawUI(analyzeResults)
41+
} else {
42+
return nil
43+
}
44+
case "s":
45+
filename, err := save(analyzeResults)
46+
if err != nil {
47+
// show
48+
} else {
49+
showSaved(filename)
50+
go func() {
51+
time.Sleep(time.Second * 5)
52+
isShowingSaved = false
53+
ui.Clear()
54+
drawUI(analyzeResults)
55+
}()
56+
}
57+
case "<Resize>":
58+
ui.Clear()
59+
drawUI(analyzeResults)
60+
case "<Down>":
61+
if selectedResult < len(analyzeResults)-1 {
62+
selectedResult++
63+
} else {
64+
selectedResult = 0
65+
}
66+
ui.Clear()
67+
drawUI(analyzeResults)
68+
case "<Up>":
69+
if selectedResult > 0 {
70+
selectedResult--
71+
} else {
72+
selectedResult = len(analyzeResults) - 1
73+
}
74+
ui.Clear()
75+
drawUI(analyzeResults)
76+
}
77+
}
78+
}
79+
}
80+
81+
func drawUI(analyzeResults []*analyzerunner.AnalyzeResult) {
82+
drawGrid(analyzeResults)
83+
drawFooter()
84+
}
85+
86+
func drawGrid(analyzeResults []*analyzerunner.AnalyzeResult) {
87+
termWidth, _ := ui.TerminalDimensions()
88+
89+
tileWidth := 40
90+
tileHeight := 10
91+
92+
columnCount := termWidth / tileWidth
93+
94+
row := 0
95+
col := 0
96+
97+
for _, analyzeResult := range analyzeResults {
98+
// draw this file
99+
100+
tile := widgets.NewParagraph()
101+
tile.Title = analyzeResult.Title
102+
tile.Text = analyzeResult.Message
103+
tile.PaddingLeft = 1
104+
tile.PaddingBottom = 1
105+
tile.PaddingRight = 1
106+
tile.PaddingTop = 1
107+
108+
tile.SetRect(col*tileWidth, row*tileHeight, col*tileWidth+tileWidth, row*tileHeight+tileHeight)
109+
110+
if analyzeResult.IsFail {
111+
tile.BorderStyle.Fg = ui.ColorRed
112+
} else if analyzeResult.IsWarn {
113+
tile.BorderStyle.Fg = ui.ColorYellow
114+
} else {
115+
tile.BorderStyle.Fg = ui.ColorGreen
116+
}
117+
118+
ui.Render(tile)
119+
120+
col++
121+
122+
if col >= columnCount {
123+
col = 0
124+
row++
125+
}
126+
}
127+
}
128+
129+
func drawFooter() {
130+
termWidth, termHeight := ui.TerminalDimensions()
131+
132+
instructions := widgets.NewParagraph()
133+
instructions.Text = "[q] quit [s] save [↑][↓] scroll"
134+
instructions.Border = false
135+
136+
left := 0
137+
right := termWidth
138+
top := termHeight - 1
139+
bottom := termHeight
140+
141+
instructions.SetRect(left, top, right, bottom)
142+
ui.Render(instructions)
143+
}
144+
145+
func showSaved(filename string) {
146+
termWidth, termHeight := ui.TerminalDimensions()
147+
148+
savedMessage := widgets.NewParagraph()
149+
savedMessage.Text = fmt.Sprintf("Preflight results saved to\n\n%s", filename)
150+
savedMessage.WrapText = true
151+
savedMessage.Border = true
152+
153+
left := termWidth/2 - 20
154+
right := termWidth/2 + 20
155+
top := termHeight/2 - 4
156+
bottom := termHeight/2 + 4
157+
158+
savedMessage.SetRect(left, top, right, bottom)
159+
ui.Render(savedMessage)
160+
161+
isShowingSaved = true
162+
}
163+
164+
func save(analyzeResults []*analyzerunner.AnalyzeResult) (string, error) {
165+
filename := path.Join(util.HomeDir(), fmt.Sprintf("%s-results.txt", "support-bundle"))
166+
_, err := os.Stat(filename)
167+
if err == nil {
168+
os.Remove(filename)
169+
}
170+
171+
results := ""
172+
for _, analyzeResult := range analyzeResults {
173+
result := ""
174+
175+
if analyzeResult.IsPass {
176+
result = "Check PASS\n"
177+
} else if analyzeResult.IsWarn {
178+
result = "Check WARN\n"
179+
} else if analyzeResult.IsFail {
180+
result = "Check FAIL\n"
181+
}
182+
183+
result = result + fmt.Sprintf("Title: %s\n", analyzeResult.Title)
184+
result = result + fmt.Sprintf("Message: %s\n", analyzeResult.Message)
185+
186+
if analyzeResult.URI != "" {
187+
result = result + fmt.Sprintf("URI: %s\n", analyzeResult.URI)
188+
}
189+
190+
result = result + "\n------------\n"
191+
192+
results = results + result
193+
}
194+
195+
if err := ioutil.WriteFile(filename, []byte(results), 0644); err != nil {
196+
return "", errors.Wrap(err, "failed to save preflight results")
197+
}
198+
199+
return filename, nil
200+
}

cmd/troubleshoot/cli/run.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
9999
s := spin.New()
100100
finishedCh := make(chan bool, 1)
101101
progressChan := make(chan interface{}, 0) // non-zero buffer can result in missed messages
102+
isFinishedChClosed := false
102103
go func() {
103104
currentDir := ""
104105
for {
@@ -124,7 +125,9 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
124125
}
125126
}()
126127
defer func() {
127-
close(finishedCh)
128+
if !isFinishedChClosed {
129+
close(finishedCh)
130+
}
128131
}()
129132

130133
archivePath, err := runCollectors(v, supportBundleSpec.Spec.Collectors, additionalRedactors, progressChan)
@@ -180,14 +183,27 @@ func runTroubleshoot(v *viper.Viper, arg string) error {
180183
c.Printf("%s\r * Failed to analyze support bundle: %v\n", cursor.ClearEntireLine(), err)
181184
}
182185

183-
data := convert.FromAnalyzerResult(analyzeResults)
184-
formatted, err := json.MarshalIndent(data, "", " ")
185-
if err != nil {
186-
c := color.New(color.FgHiRed)
187-
c.Printf("%s\r * Failed to format analysis: %v\n", cursor.ClearEntireLine(), err)
186+
interactive := isatty.IsTerminal(os.Stdout.Fd())
187+
188+
if interactive {
189+
close(finishedCh) // this removes the spinner
190+
isFinishedChClosed = true
191+
192+
if err := showInteractiveResults(analyzeResults); err != nil {
193+
interactive = false
194+
}
188195
}
189196

190-
fmt.Printf("%s", formatted)
197+
if !interactive {
198+
data := convert.FromAnalyzerResult(analyzeResults)
199+
formatted, err := json.MarshalIndent(data, "", " ")
200+
if err != nil {
201+
c := color.New(color.FgHiRed)
202+
c.Printf("%s\r * Failed to format analysis: %v\n", cursor.ClearEntireLine(), err)
203+
}
204+
205+
fmt.Printf("%s", formatted)
206+
}
191207
}
192208

193209
if !fileUploaded {

0 commit comments

Comments
 (0)