1package logs
2
3import (
4 "encoding/json"
5 "slices"
6
7 "github.com/charmbracelet/bubbles/v2/key"
8 "github.com/charmbracelet/bubbles/v2/table"
9 tea "github.com/charmbracelet/bubbletea/v2"
10 "github.com/charmbracelet/crush/internal/logging"
11 "github.com/charmbracelet/crush/internal/pubsub"
12 "github.com/charmbracelet/crush/internal/tui/layout"
13 "github.com/charmbracelet/crush/internal/tui/styles"
14 "github.com/charmbracelet/crush/internal/tui/util"
15)
16
17type TableComponent interface {
18 util.Model
19 layout.Sizeable
20 layout.Bindings
21}
22
23type tableCmp struct {
24 table table.Model
25}
26
27type selectedLogMsg logging.LogMessage
28
29func (i *tableCmp) Init() tea.Cmd {
30 i.setRows()
31 return nil
32}
33
34func (i *tableCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
35 var cmds []tea.Cmd
36 switch msg.(type) {
37 case pubsub.Event[logging.LogMessage]:
38 i.setRows()
39 return i, nil
40 }
41 prevSelectedRow := i.table.SelectedRow()
42 t, cmd := i.table.Update(msg)
43 cmds = append(cmds, cmd)
44 i.table = t
45 selectedRow := i.table.SelectedRow()
46 if selectedRow != nil {
47 if prevSelectedRow == nil || selectedRow[0] == prevSelectedRow[0] {
48 var log logging.LogMessage
49 for _, row := range logging.List() {
50 if row.ID == selectedRow[0] {
51 log = row
52 break
53 }
54 }
55 if log.ID != "" {
56 cmds = append(cmds, util.CmdHandler(selectedLogMsg(log)))
57 }
58 }
59 }
60 return i, tea.Batch(cmds...)
61}
62
63func (i *tableCmp) View() tea.View {
64 t := styles.CurrentTheme()
65 defaultStyles := table.DefaultStyles()
66 defaultStyles.Selected = defaultStyles.Selected.Foreground(t.Primary)
67 i.table.SetStyles(defaultStyles)
68 return tea.NewView(i.table.View())
69}
70
71func (i *tableCmp) GetSize() (int, int) {
72 return i.table.Width(), i.table.Height()
73}
74
75func (i *tableCmp) SetSize(width int, height int) tea.Cmd {
76 i.table.SetWidth(width)
77 i.table.SetHeight(height)
78 cloumns := i.table.Columns()
79 for i, col := range cloumns {
80 col.Width = (width / len(cloumns)) - 2
81 cloumns[i] = col
82 }
83 i.table.SetColumns(cloumns)
84 return nil
85}
86
87func (i *tableCmp) BindingKeys() []key.Binding {
88 return layout.KeyMapToSlice(i.table.KeyMap)
89}
90
91func (i *tableCmp) setRows() {
92 rows := []table.Row{}
93
94 logs := logging.List()
95 slices.SortFunc(logs, func(a, b logging.LogMessage) int {
96 if a.Time.Before(b.Time) {
97 return 1
98 }
99 if a.Time.After(b.Time) {
100 return -1
101 }
102 return 0
103 })
104
105 for _, log := range logs {
106 bm, _ := json.Marshal(log.Attributes)
107
108 row := table.Row{
109 log.ID,
110 log.Time.Format("15:04:05"),
111 log.Level,
112 log.Message,
113 string(bm),
114 }
115 rows = append(rows, row)
116 }
117 i.table.SetRows(rows)
118}
119
120func NewLogsTable() TableComponent {
121 columns := []table.Column{
122 {Title: "ID", Width: 4},
123 {Title: "Time", Width: 4},
124 {Title: "Level", Width: 10},
125 {Title: "Message", Width: 10},
126 {Title: "Attributes", Width: 10},
127 }
128
129 tableModel := table.New(
130 table.WithColumns(columns),
131 )
132 tableModel.Focus()
133 return &tableCmp{
134 table: tableModel,
135 }
136}