terminal.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. /*
  2. interactive terminal interface for local clients
  3. */
  4. package ui
  5. import (
  6. "fmt"
  7. termbox "github.com/nsf/termbox-go"
  8. "time"
  9. )
  10. const (
  11. fgColor = termbox.ColorWhite
  12. bgColor = termbox.ColorDefault
  13. )
  14. func clear() {
  15. w, h := termbox.Size()
  16. for i := 0; i < w; i++ {
  17. for j := 0; j < h; j++ {
  18. termbox.SetCell(i, j, ' ', fgColor, bgColor)
  19. }
  20. }
  21. }
  22. func printfAttr(x, y int, fg termbox.Attribute, arg0 string, args ...interface{}) {
  23. s := fmt.Sprintf(arg0, args...)
  24. for i, ch := range s {
  25. termbox.SetCell(x+i, y, ch, fg, bgColor)
  26. }
  27. }
  28. func printf(x, y int, arg0 string, args ...interface{}) {
  29. printfAttr(x, y, fgColor, arg0, args...)
  30. }
  31. type Term struct {
  32. ui *Ui
  33. statusColorMap map[string]termbox.Attribute
  34. updates chan (State)
  35. }
  36. func NewTerm() *Term {
  37. return &Term{
  38. statusColorMap: map[string]termbox.Attribute{
  39. "connecting": termbox.ColorCyan,
  40. "reconnecting": termbox.ColorRed,
  41. "online": termbox.ColorGreen,
  42. },
  43. updates: make(chan State),
  44. }
  45. }
  46. func (t *Term) SetUi(ui *Ui) {
  47. t.ui = ui
  48. go t.run()
  49. }
  50. func (t *Term) run() {
  51. // make sure we shut down cleanly
  52. t.ui.Wait.Add(1)
  53. defer t.ui.Wait.Done()
  54. // open channels for incoming application state changes
  55. // and broadbasts
  56. t.updates = t.ui.Updates.Reg()
  57. // init/close termbox library
  58. termbox.Init()
  59. defer termbox.Close()
  60. go t.input()
  61. t.draw()
  62. }
  63. func (t *Term) draw() {
  64. var state State
  65. for {
  66. select {
  67. case newState := <-t.updates:
  68. if newState != nil {
  69. state = newState
  70. }
  71. if state == nil {
  72. // log.Info("Got update to draw, but no state to draw with")
  73. continue
  74. }
  75. // program is shutting down
  76. if state.IsStopping() {
  77. return
  78. }
  79. clear()
  80. x, _ := termbox.Size()
  81. quitMsg := "(Ctrl+C to quit)"
  82. printf(x-len(quitMsg), 0, quitMsg)
  83. printfAttr(0, 0, termbox.ColorBlue|termbox.AttrBold, "ngrok")
  84. msec := float64(time.Millisecond)
  85. printfAttr(0, 2, t.statusColorMap[state.GetStatus()], "%-30s%s", "Tunnel Status", state.GetStatus())
  86. printf(0, 3, "%-30s%s", "Version", state.GetVersion())
  87. printf(0, 4, "%-30s%s", "Protocol", state.GetProtocol())
  88. printf(0, 5, "%-30s%s -> %s", "Forwarding", state.GetPublicUrl(), state.GetLocalAddr())
  89. printf(0, 6, "%-30s%s", "HTTP Dashboard", "http://127.0.0.1:9999")
  90. connMeter, connTimer := state.GetConnectionMetrics()
  91. printf(0, 7, "%-30s%d", "# Conn", connMeter.Count())
  92. printf(0, 8, "%-30s%.2fms", "Avg Conn Time", connTimer.Mean()/msec)
  93. if state.GetProtocol() == "http" {
  94. printf(0, 10, "HTTP Requests")
  95. printf(0, 11, "-------------")
  96. for i, http := range state.GetHistory() {
  97. req := http.GetRequest()
  98. resp := http.GetResponse()
  99. printf(0, 12+i, "%s %v", req.Method, req.URL)
  100. if resp != nil {
  101. printf(30, 12+i, "%s", resp.Status)
  102. }
  103. }
  104. }
  105. termbox.Flush()
  106. }
  107. }
  108. }
  109. func (t *Term) input() {
  110. for {
  111. ev := termbox.PollEvent()
  112. switch ev.Type {
  113. case termbox.EventKey:
  114. switch ev.Key {
  115. case termbox.KeyCtrlC:
  116. t.ui.Cmds <- Command{QUIT, ""}
  117. return
  118. }
  119. case termbox.EventResize:
  120. t.updates <- nil
  121. case termbox.EventError:
  122. panic(ev.Err)
  123. }
  124. }
  125. }