1package logs
2
3import (
4 "fmt"
5 "strings"
6 "time"
7
8 "github.com/charmbracelet/bubbles/key"
9 "github.com/charmbracelet/bubbles/viewport"
10 tea "github.com/charmbracelet/bubbletea"
11 "github.com/charmbracelet/lipgloss"
12 "github.com/kujtimiihoxha/termai/internal/logging"
13 "github.com/kujtimiihoxha/termai/internal/tui/layout"
14 "github.com/kujtimiihoxha/termai/internal/tui/styles"
15)
16
17type DetailComponent interface {
18 tea.Model
19 layout.Focusable
20 layout.Sizeable
21 layout.Bindings
22 layout.Bordered
23}
24
25type detailCmp struct {
26 width, height int
27 focused bool
28 currentLog logging.LogMessage
29 viewport viewport.Model
30}
31
32func (i *detailCmp) Init() tea.Cmd {
33 messages := logging.Get().List()
34 if len(messages) == 0 {
35 return nil
36 }
37 i.currentLog = messages[0]
38 return nil
39}
40
41func (i *detailCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
42 var (
43 cmd tea.Cmd
44 cmds []tea.Cmd
45 )
46
47 switch msg := msg.(type) {
48 case selectedLogMsg:
49 if msg.ID != i.currentLog.ID {
50 i.currentLog = logging.LogMessage(msg)
51 i.updateContent()
52 }
53 }
54
55 if i.focused {
56 i.viewport, cmd = i.viewport.Update(msg)
57 cmds = append(cmds, cmd)
58 }
59
60 return i, tea.Batch(cmds...)
61}
62
63func (i *detailCmp) updateContent() {
64 var content strings.Builder
65
66 // Format the header with timestamp and level
67 timeStyle := lipgloss.NewStyle().Foreground(styles.SubText0)
68 levelStyle := getLevelStyle(i.currentLog.Level)
69
70 header := lipgloss.JoinHorizontal(
71 lipgloss.Center,
72 timeStyle.Render(i.currentLog.Time.Format(time.RFC3339)),
73 " ",
74 levelStyle.Render(i.currentLog.Level),
75 )
76
77 content.WriteString(lipgloss.NewStyle().Bold(true).Render(header))
78 content.WriteString("\n\n")
79
80 // Message with styling
81 messageStyle := lipgloss.NewStyle().Bold(true).Foreground(styles.Text)
82 content.WriteString(messageStyle.Render("Message:"))
83 content.WriteString("\n")
84 content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(i.currentLog.Message))
85 content.WriteString("\n\n")
86
87 // Attributes section
88 if len(i.currentLog.Attributes) > 0 {
89 attrHeaderStyle := lipgloss.NewStyle().Bold(true).Foreground(styles.Text)
90 content.WriteString(attrHeaderStyle.Render("Attributes:"))
91 content.WriteString("\n")
92
93 // Create a table-like display for attributes
94 keyStyle := lipgloss.NewStyle().Foreground(styles.Primary).Bold(true)
95 valueStyle := lipgloss.NewStyle().Foreground(styles.Text)
96
97 for _, attr := range i.currentLog.Attributes {
98 attrLine := fmt.Sprintf("%s: %s",
99 keyStyle.Render(attr.Key),
100 valueStyle.Render(attr.Value),
101 )
102 content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(attrLine))
103 content.WriteString("\n")
104 }
105 }
106
107 i.viewport.SetContent(content.String())
108}
109
110func getLevelStyle(level string) lipgloss.Style {
111 style := lipgloss.NewStyle().Bold(true)
112
113 switch strings.ToLower(level) {
114 case "info":
115 return style.Foreground(styles.Blue)
116 case "warn", "warning":
117 return style.Foreground(styles.Warning)
118 case "error", "err":
119 return style.Foreground(styles.Error)
120 case "debug":
121 return style.Foreground(styles.Green)
122 default:
123 return style.Foreground(styles.Text)
124 }
125}
126
127func (i *detailCmp) View() string {
128 return i.viewport.View()
129}
130
131func (i *detailCmp) Blur() tea.Cmd {
132 i.focused = false
133 return nil
134}
135
136func (i *detailCmp) Focus() tea.Cmd {
137 i.focused = true
138 return nil
139}
140
141func (i *detailCmp) IsFocused() bool {
142 return i.focused
143}
144
145func (i *detailCmp) GetSize() (int, int) {
146 return i.width, i.height
147}
148
149func (i *detailCmp) SetSize(width int, height int) {
150 i.width = width
151 i.height = height
152 i.viewport.Width = i.width
153 i.viewport.Height = i.height
154 i.updateContent()
155}
156
157func (i *detailCmp) BindingKeys() []key.Binding {
158 return []key.Binding{
159 i.viewport.KeyMap.PageDown,
160 i.viewport.KeyMap.PageUp,
161 i.viewport.KeyMap.HalfPageDown,
162 i.viewport.KeyMap.HalfPageUp,
163 }
164}
165
166func (i *detailCmp) BorderText() map[layout.BorderPosition]string {
167 return map[layout.BorderPosition]string{
168 layout.TopLeftBorder: "Log Details",
169 }
170}
171
172func NewLogsDetails() DetailComponent {
173 return &detailCmp{
174 viewport: viewport.New(0, 0),
175 }
176}