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