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