Skip to content

Commit 516e790

Browse files
author
JkLondon
committed
commit
1 parent 2cada03 commit 516e790

File tree

5 files changed

+732
-12
lines changed

5 files changed

+732
-12
lines changed

cmd/etui/app/app.go

Lines changed: 119 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ type App struct {
2626
iopsTrack *datasource.DiskIOPSTracker
2727
syncTracker *datasource.SyncTracker
2828
alertMgr *datasource.AlertManager
29+
logTailer *datasource.LogTailer
2930
datadir string
3031
}
3132

3233
// New creates an App that reads from the given datadir.
3334
func New(datadir string) *App {
35+
logPath := filepath.Join(datadir, "logs", "erigon.log")
3436
return &App{
3537
datadir: datadir,
3638
tview: tview.NewApplication(),
@@ -40,9 +42,17 @@ func New(datadir string) *App {
4042
iopsTrack: datasource.NewDiskIOPSTracker(),
4143
syncTracker: datasource.NewSyncTracker(),
4244
alertMgr: datasource.NewAlertManager(),
45+
logTailer: datasource.NewLogTailer(logPath),
4346
}
4447
}
4548

49+
// Page name constants.
50+
const (
51+
pageStart = "start"
52+
pageNodeInfo = "nodeInfo"
53+
pageLogs = "logs"
54+
)
55+
4656
// Run starts the TUI event loop. It blocks until the user quits or the parent
4757
// context is cancelled (e.g. by an OS signal).
4858
func (a *App) Run(parent context.Context, infoCh <-chan *commands.StagesInfo, errCh chan error) error {
@@ -66,6 +76,25 @@ func (a *App) Run(parent context.Context, infoCh <-chan *commands.StagesInfo, er
6676
AddItem(nodeInfoBody, 0, 5, false).
6777
AddItem(footer, 2, 1, false)
6878

79+
// Log viewer page (full-screen)
80+
// currentPage is captured by the closures below, so declare it here.
81+
dashPages := []string{pageStart, pageNodeInfo}
82+
currentPage := 0
83+
84+
var logViewer *widgets.LogViewerPage
85+
switchToDashboard := func() {
86+
currentPage = 1 // nodeInfo
87+
pages.SwitchToPage(pageNodeInfo)
88+
a.tview.SetFocus(pages)
89+
}
90+
logViewer = widgets.NewLogViewerPage(switchToDashboard)
91+
logsPage := tview.NewFlex().SetDirection(tview.FlexRow).
92+
AddItem(widgets.Header(), 1, 1, false).
93+
AddItem(logViewer.Root, 0, 1, true)
94+
95+
// Seed the log tailer so the viewer has content immediately.
96+
a.logTailer.SeedFromEnd()
97+
6998
// Start background goroutines
7099
go a.safeGo("fillStagesInfo", errCh, func() { a.fillStagesInfo(ctx, nodeView, infoCh) })
71100
go a.safeGo("runClock", errCh, func() { a.runClock(ctx, nodeView.Clock) })
@@ -74,25 +103,65 @@ func (a *App) Run(parent context.Context, infoCh <-chan *commands.StagesInfo, er
74103
go a.safeGo("pollSystemHealth", errCh, func() { a.pollSystemHealth(ctx, nodeView.SystemHealth) })
75104
go a.safeGo("pollAlerts", errCh, func() { a.pollAlerts(ctx, nodeView.Alerts) })
76105
go a.safeGo("pollLogTail", errCh, func() { a.pollLogTail(ctx, nodeView.LogTail) })
106+
go a.safeGo("pollLogViewer", errCh, func() { a.pollLogViewer(ctx, logViewer) })
77107

78-
// Page navigation
79-
currentPage, pagesCount := 0, 2
80-
names := []string{"start", "nodeInfo"}
81-
pages.AddPage(names[0], startPage, true, true)
82-
pages.AddPage(names[1], nodeInfoPage, true, false)
108+
pages.AddPage(pageStart, startPage, true, true)
109+
pages.AddPage(pageNodeInfo, nodeInfoPage, true, false)
110+
pages.AddPage(pageLogs, logsPage, true, false)
83111

84112
if err := a.tview.SetRoot(pages, true).EnableMouse(true).SetInputCapture(
85113
func(event *tcell.EventKey) *tcell.EventKey {
114+
currentFront, _ := pages.GetFrontPage()
115+
116+
// --- Log viewer page input handling ---
117+
if currentFront == pageLogs {
118+
// When search bar is focused, let it handle all input except Escape.
119+
if logViewer.IsSearching() {
120+
if event.Key() == tcell.KeyEscape {
121+
logViewer.DismissSearch()
122+
a.tview.SetFocus(logViewer.Content())
123+
return nil
124+
}
125+
if event.Key() == tcell.KeyEnter {
126+
logViewer.DismissSearch()
127+
a.tview.SetFocus(logViewer.Content())
128+
return nil
129+
}
130+
return event // let InputField handle typing
131+
}
132+
133+
switch {
134+
case event.Key() == tcell.KeyCtrlC || event.Rune() == 'q':
135+
cancel()
136+
a.tview.Stop()
137+
return nil
138+
case event.Key() == tcell.KeyEscape || event.Key() == tcell.KeyF1:
139+
switchToDashboard()
140+
return nil
141+
case event.Rune() == '/':
142+
logViewer.EnterSearchMode()
143+
a.tview.SetFocus(logViewer.SearchBar())
144+
return nil
145+
}
146+
// Remaining keys (1-4, Space, arrows) handled by content's InputCapture.
147+
return event
148+
}
149+
150+
// --- Dashboard pages input handling ---
86151
switch {
87152
case event.Key() == tcell.KeyCtrlC || event.Rune() == 'q':
88153
cancel()
89154
a.tview.Stop()
155+
case event.Key() == tcell.KeyF2 || event.Rune() == 'L':
156+
pages.SwitchToPage(pageLogs)
157+
a.tview.SetFocus(logViewer.Content())
158+
return nil
90159
case event.Key() == tcell.KeyRight:
91-
currentPage = (currentPage + 1 + pagesCount) % pagesCount
92-
pages.SwitchToPage(names[currentPage])
160+
currentPage = (currentPage + 1) % len(dashPages)
161+
pages.SwitchToPage(dashPages[currentPage])
93162
case event.Key() == tcell.KeyLeft:
94-
currentPage = (currentPage - 1 + pagesCount) % pagesCount
95-
pages.SwitchToPage(names[currentPage])
163+
currentPage = (currentPage - 1 + len(dashPages)) % len(dashPages)
164+
pages.SwitchToPage(dashPages[currentPage])
96165
}
97166
return event
98167
}).Run(); err != nil {
@@ -346,3 +415,44 @@ func (a *App) pollLogTail(ctx context.Context, view *widgets.LogTailView) {
346415
}
347416
}
348417
}
418+
419+
// pollLogViewer periodically tails the log file via LogTailer and updates
420+
// the full-screen log viewer widget.
421+
func (a *App) pollLogViewer(ctx context.Context, viewer *widgets.LogViewerPage) {
422+
const pollInterval = 500 * time.Millisecond
423+
ticker := time.NewTicker(pollInterval)
424+
defer ticker.Stop()
425+
426+
var lastVersion int64
427+
428+
// Initial render from seeded data.
429+
lines := a.logTailer.Recent(logRingSize, viewer.FilterLevel())
430+
a.tview.QueueUpdateDraw(func() {
431+
viewer.UpdateContent(lines)
432+
})
433+
lastVersion = a.logTailer.Version()
434+
435+
for {
436+
select {
437+
case <-ctx.Done():
438+
return
439+
case <-ticker.C:
440+
// Poll for new file content (I/O happens here, not on event loop).
441+
a.logTailer.Poll()
442+
443+
v := a.logTailer.Version()
444+
if v == lastVersion {
445+
continue
446+
}
447+
lastVersion = v
448+
449+
lines := a.logTailer.Recent(logRingSize, viewer.FilterLevel())
450+
a.tview.QueueUpdateDraw(func() {
451+
viewer.UpdateContent(lines)
452+
})
453+
}
454+
}
455+
}
456+
457+
// logRingSize matches the datasource ring buffer size for the full viewer.
458+
const logRingSize = 1000

0 commit comments

Comments
 (0)