feat(tui): mouse click for settings sections, column sort, logs panel
CI / test (pull_request) Successful in 1m44s
CI / lint (pull_request) Successful in 1m17s
CI / vulncheck (pull_request) Successful in 56s

- Click Alerts/Nodes/Users sub-tabs in settings to switch sections
- Click STATUS/NAME/LATENCY column headers to sort (click again
  to reverse direction)
- Logs panel show/hide: [l] toggles sidebar visibility, click
  focused panel to hide. Monitors expand to full width when hidden.
- Logs full-screen: Enter when focused on logs opens scrollable
  full-screen log view with severity tags, filter toggle [f],
  and live updates
This commit was merged in pull request #159.
This commit is contained in:
2026-06-28 11:34:34 -04:00
parent ab1194f74d
commit 18e2d60210
6 changed files with 120 additions and 4 deletions
+80
View File
@@ -53,6 +53,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.state == stateFormSite || m.state == stateFormAlert || m.state == stateFormUser || m.state == stateFormMaint {
return m.handleFormMsg(msg)
}
if m.state == stateLogs {
return m.handleLogsFullscreen(msg)
}
switch msg := msg.(type) {
case tea.MouseMsg:
@@ -241,6 +244,40 @@ func (m *Model) testAlertCmd(id int, name string) tea.Cmd {
}
}
func (m *Model) handleLogsFullscreen(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "esc", "q":
m.state = stateDashboard
m.focusedPanel = panelLogs
case "ctrl+c":
return m, tea.Quit
case "f":
m.logFilterImportant = !m.logFilterImportant
m.refreshLogContent()
case "up", "k":
m.logViewport.ScrollUp(1)
case "down", "j":
m.logViewport.ScrollDown(1)
case "pgup":
m.logViewport.ScrollUp(m.logViewport.Height)
case "pgdown":
m.logViewport.ScrollDown(m.logViewport.Height)
}
case tea.MouseMsg:
switch msg.Button {
case tea.MouseButtonWheelUp:
m.logViewport.ScrollUp(3)
case tea.MouseButtonWheelDown:
m.logViewport.ScrollDown(3)
}
case tickMsg:
m.refreshLogContent()
}
return m, nil
}
func (m *Model) handleMouse(msg tea.MouseMsg) (tea.Model, tea.Cmd) {
if m.state == stateHistory {
switch msg.Button {
@@ -579,10 +616,13 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.switchSettingsSection(m.settingsSection + 1)
case tabMonitors:
if m.focusedPanel == panelLogs {
m.logsOpen = false
m.focusedPanel = panelMonitors
} else {
m.logsOpen = true
m.focusedPanel = panelLogs
}
m.recalcLayout()
}
case "up", "k":
if m.currentTab == tabMonitors && m.focusedPanel == panelLogs {
@@ -618,6 +658,12 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
case "n":
return m.handleNewItem()
case "enter":
if m.currentTab == tabMonitors && m.focusedPanel == panelLogs {
m.refreshLogContent()
m.logViewport.GotoTop()
m.state = stateLogs
return m, nil
}
if m.currentTab == tabMonitors && len(m.sites) > 0 {
m.state = stateDetail
return m, m.loadDetailCmd(m.sites[m.cursor].ID)
@@ -840,7 +886,41 @@ func (m *Model) handleClick(msg tea.MouseMsg) (tea.Model, tea.Cmd) {
}
}
if m.currentTab == tabSettings {
maxSec := 1
if m.isAdmin {
maxSec = 2
}
for i := 0; i <= maxSec; i++ {
if m.zones.Get(fmt.Sprintf("section-%d", i)).InBounds(msg) {
m.switchSettingsSection(i)
return m, nil
}
}
}
if m.currentTab == tabMonitors {
sortZones := []struct {
zone string
col int
}{
{"sort-status", sortStatus},
{"sort-name", sortName},
{"sort-latency", sortLatency},
}
for _, sz := range sortZones {
if m.zones.Get(sz.zone).InBounds(msg) {
if m.sortColumn == sz.col {
m.sortAsc = !m.sortAsc
} else {
m.sortColumn = sz.col
m.sortAsc = false
}
m.refreshLive()
return m, nil
}
}
if m.zones.Get("panel-monitors").InBounds(msg) {
m.focusedPanel = panelMonitors
} else if m.zones.Get("panel-logs").InBounds(msg) {