view.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. package tableview
  2. import (
  3. "fmt"
  4. "strings"
  5. "fbtop/internal/shared"
  6. "charm.land/lipgloss/v2"
  7. )
  8. var (
  9. headerStyle = lipgloss.NewStyle().Bold(true)
  10. selectedStyle = lipgloss.NewStyle().Reverse(true)
  11. )
  12. type Params struct {
  13. Comps []shared.ComponentMetrics
  14. PrevComps []shared.ComponentMetrics
  15. Width int
  16. Cursor int
  17. Humanize bool
  18. }
  19. var (
  20. healthOKStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("42"))
  21. healthWarnStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("214"))
  22. healthErrStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("196"))
  23. healthIdleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("245"))
  24. )
  25. func Render(p Params) string {
  26. prevMap := make(map[string]shared.ComponentMetrics, len(p.PrevComps))
  27. for _, c := range p.PrevComps {
  28. prevMap[c.ID] = c
  29. }
  30. var b strings.Builder
  31. b.WriteString(headerStyle.Render(fmt.Sprintf(" %-4s %-1s %-24s %-7s %10s %10s %s", "#", "", "Component", "Kind", "Rec/s", "Bytes/s", "Extra")))
  32. b.WriteString("\n")
  33. b.WriteString(strings.Repeat("─", p.Width))
  34. b.WriteString("\n")
  35. var lastKind shared.Kind = -1
  36. for i, c := range p.Comps {
  37. if c.Kind != lastKind {
  38. b.WriteString("\n")
  39. b.WriteString(headerStyle.Render(" " + sectionName(c.Kind)))
  40. b.WriteString("\n")
  41. lastKind = c.Kind
  42. }
  43. // Rates from delta
  44. recRate, byteRate := "-", "-"
  45. if prev, ok := prevMap[c.ID]; ok {
  46. dt := c.Stamp.Sub(prev.Stamp)
  47. if dt > 0 {
  48. recRate = shared.FormatRate(shared.Rate(prev.Records(), c.Records(), dt), p.Humanize, false)
  49. byteRate = shared.FormatRate(shared.Rate(prev.Bytes(), c.Bytes(), dt), p.Humanize, true)
  50. }
  51. }
  52. // Extra column
  53. extra := "-"
  54. switch c.Kind {
  55. case shared.KindOutput:
  56. var parts []string
  57. if e := c.Fields["errors"]; e > 0 {
  58. parts = append(parts, fmt.Sprintf("err:%d", e))
  59. }
  60. if r := c.Fields["retries"]; r > 0 {
  61. parts = append(parts, fmt.Sprintf("ret:%d", r))
  62. }
  63. if len(parts) > 0 {
  64. extra = strings.Join(parts, " ")
  65. }
  66. case shared.KindFilter:
  67. if d := c.Fields["drop_records"]; d > 0 {
  68. extra = fmt.Sprintf("drop:%d", d)
  69. }
  70. }
  71. // Health badge
  72. health := shared.ComponentHealth(c, prevMap[c.ID])
  73. badge := healthBadge(health)
  74. row := fmt.Sprintf(" %-4d %-1s %-24s %-7s %10s %10s %s", i+1, badge, c.ID, c.Kind, recRate, byteRate, extra)
  75. if i == p.Cursor {
  76. row = selectedStyle.Render(row)
  77. }
  78. b.WriteString(row)
  79. b.WriteString("\n")
  80. }
  81. return b.String()
  82. }
  83. func sectionName(k shared.Kind) string {
  84. switch k {
  85. case shared.KindInput:
  86. return "SOURCES"
  87. case shared.KindFilter:
  88. return "FILTERS"
  89. case shared.KindOutput:
  90. return "OUTPUTS"
  91. default:
  92. return "ALL"
  93. }
  94. }
  95. func healthBadge(h shared.Health) string {
  96. switch h {
  97. case shared.HealthOK:
  98. return healthOKStyle.Render("●")
  99. case shared.HealthWarning:
  100. return healthWarnStyle.Render("▲")
  101. case shared.HealthError:
  102. return healthErrStyle.Render("✖")
  103. case shared.HealthIdle:
  104. return healthIdleStyle.Render("○")
  105. default:
  106. return " "
  107. }
  108. }