feat(tui): consolidate 6 tabs to 3, add log sidebar

Tab bar: Monitors | Maint | Settings (was 6 tabs).

Settings tab merges Alerts, Nodes, Users as sub-sections with
left/right arrow navigation. Each section keeps its own cursor,
keybindings, and CRUD operations.

Monitors tab now shows a log sidebar at >= 120 cols (70/30 split).
Under 120 cols, monitors render full-width without logs.

- Introduced tab constants (tabMonitors, tabMaint, tabSettings)
- Introduced section constants (sectionAlerts, sectionNodes, sectionUsers)
- Removed stateLogs and stateUsers states
- All magic tab numbers replaced with named constants
This commit is contained in:
2026-06-20 17:59:47 -04:00
parent 5398cccd44
commit 047bb237e0
6 changed files with 224 additions and 171 deletions
+2 -2
View File
@@ -106,7 +106,7 @@ func (m *Model) refreshLive() {
m.sites = ordered m.sites = ordered
m.refreshLogContent() m.refreshLogContent()
if m.currentTab == 0 && m.selectedID != 0 { if m.currentTab == tabMonitors && m.selectedID != 0 {
for i, s := range m.sites { for i, s := range m.sites {
if s.ID == m.selectedID { if s.ID == m.selectedID {
m.cursor = i m.cursor = i
@@ -118,7 +118,7 @@ func (m *Model) refreshLive() {
} }
func (m *Model) syncSelectedID() { func (m *Model) syncSelectedID() {
if m.currentTab == 0 && m.cursor < len(m.sites) { if m.currentTab == tabMonitors && m.cursor < len(m.sites) {
m.selectedID = m.sites[m.cursor].ID m.selectedID = m.sites[m.cursor].ID
} }
} }
+58
View File
@@ -0,0 +1,58 @@
package tui
import (
"github.com/charmbracelet/lipgloss"
)
func (m Model) viewSettingsTab() string {
maxSections := 2
if m.isAdmin {
maxSections = 3
}
sections := []string{"Alerts", "Nodes"}
if m.isAdmin {
sections = append(sections, "Users")
}
_ = maxSections
var tabs []string
for i, name := range sections {
if i == m.settingsSection {
tabs = append(tabs, m.st.activeTab.Render(name))
} else {
tabs = append(tabs, m.st.inactiveTab.Render(name))
}
}
header := lipgloss.JoinHorizontal(lipgloss.Top, tabs...)
var content string
switch m.settingsSection {
case sectionAlerts:
content = m.viewAlertsTab()
case sectionNodes:
content = m.viewNodesTab()
case sectionUsers:
if m.isAdmin {
content = m.viewUsersTab()
}
}
return header + "\n" + content
}
func (m *Model) switchSettingsSection(idx int) {
max := 1
if m.isAdmin {
max = 2
}
if idx > max {
idx = 0
}
if idx < 0 {
idx = max
}
m.settingsSection = idx
m.cursor = 0
m.tableOffset = 0
}
+1 -1
View File
@@ -116,7 +116,7 @@ func (m *Model) submitUserForm() tea.Cmd {
st := m.store st := m.store
id := m.editID id := m.editID
username, key, role := d.Username, d.PublicKey, d.Role username, key, role := d.Username, d.PublicKey, d.Role
m.state = stateUsers m.state = stateDashboard
if id > 0 { if id > 0 {
return writeCmd("Update user", func() error { return writeCmd("Update user", func() error {
return st.UpdateUser(context.Background(), id, username, key, role) return st.UpdateUser(context.Background(), id, username, key, role)
+13
View File
@@ -84,6 +84,18 @@ const (
detailSparkWidth = 40 detailSparkWidth = 40
) )
const (
tabMonitors = 0
tabMaint = 1
tabSettings = 2
)
const (
sectionAlerts = 0
sectionNodes = 1
sectionUsers = 2
)
type sessionState int type sessionState int
const ( const (
@@ -104,6 +116,7 @@ const (
type Model struct { type Model struct {
state sessionState state sessionState
currentTab int currentTab int
settingsSection int
cursor int cursor int
selectedID int selectedID int
tableOffset int tableOffset int
+81 -104
View File
@@ -78,31 +78,28 @@ func (m *Model) handleConfirmDelete(msg tea.Msg) (tea.Model, tea.Cmd) {
id := m.deleteID id := m.deleteID
var cmd tea.Cmd var cmd tea.Cmd
switch m.deleteTab { switch m.deleteTab {
case 0: case tabMonitors:
cmd = writeCmd("Delete site", func() error { return st.DeleteSite(context.Background(), id) }) cmd = writeCmd("Delete site", func() error { return st.DeleteSite(context.Background(), id) })
m.engine.RemoveSite(id) m.engine.RemoveSite(id)
m.adjustCursor(len(m.sites) - 1) m.adjustCursor(len(m.sites) - 1)
case 1: case tabMaint:
cmd = writeCmd("Delete alert", func() error { return st.DeleteAlert(context.Background(), id) })
m.adjustCursor(len(m.alerts) - 1)
case 4:
cmd = writeCmd("Delete maintenance window", func() error { return st.DeleteMaintenanceWindow(context.Background(), id) }) cmd = writeCmd("Delete maintenance window", func() error { return st.DeleteMaintenanceWindow(context.Background(), id) })
m.adjustCursor(len(m.maintenanceWindows) - 1) m.adjustCursor(len(m.maintenanceWindows) - 1)
case 5: case tabSettings:
switch m.settingsSection {
case sectionAlerts:
cmd = writeCmd("Delete alert", func() error { return st.DeleteAlert(context.Background(), id) })
m.adjustCursor(len(m.alerts) - 1)
case sectionUsers:
cmd = writeCmd("Delete user", func() error { return st.DeleteUser(context.Background(), id) }) cmd = writeCmd("Delete user", func() error { return st.DeleteUser(context.Background(), id) })
m.adjustCursor(len(m.users) - 1) m.adjustCursor(len(m.users) - 1)
} }
}
m.refreshLive() m.refreshLive()
m.state = stateDashboard m.state = stateDashboard
if m.deleteTab == 5 {
m.state = stateUsers
}
return m, cmd return m, cmd
case "n", "N", "esc": case "n", "N", "esc":
m.state = stateDashboard m.state = stateDashboard
if m.deleteTab == 5 {
m.state = stateUsers
}
case "ctrl+c": case "ctrl+c":
return m, tea.Quit return m, tea.Quit
} }
@@ -117,9 +114,6 @@ func (m *Model) handleFormMsg(msg tea.Msg) (tea.Model, tea.Cmd) {
if keyMsg.String() == "esc" { if keyMsg.String() == "esc" {
m.huhForm = nil m.huhForm = nil
m.state = stateDashboard m.state = stateDashboard
if m.currentTab == 5 {
m.state = stateUsers
}
return m, nil return m, nil
} }
} }
@@ -265,7 +259,7 @@ func (m *Model) handleMouse(msg tea.MouseMsg) (tea.Model, tea.Cmd) {
} }
return m, nil return m, nil
} }
if m.state != stateDashboard && m.state != stateLogs && m.state != stateUsers { if m.state != stateDashboard {
return m, nil return m, nil
} }
if msg.Action == tea.MouseActionPress && msg.Button == tea.MouseButtonLeft { if msg.Action == tea.MouseActionPress && msg.Button == tea.MouseButtonLeft {
@@ -275,15 +269,6 @@ func (m *Model) handleMouse(msg tea.MouseMsg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
if m.state == stateLogs {
if msg.Button == tea.MouseButtonWheelUp {
m.logViewport.ScrollUp(3)
} else {
m.logViewport.ScrollDown(3)
}
return m, nil
}
listLen := m.currentListLen() listLen := m.currentListLen()
if msg.Button == tea.MouseButtonWheelUp { if msg.Button == tea.MouseButtonWheelUp {
if m.cursor > 0 { if m.cursor > 0 {
@@ -325,7 +310,7 @@ func (m *Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m.handleSLAKey(msg) return m.handleSLAKey(msg)
case stateAlertDetail: case stateAlertDetail:
return m.handleAlertDetailKey(msg) return m.handleAlertDetailKey(msg)
case stateDashboard, stateLogs, stateUsers: case stateDashboard:
return m.handleDashboardKey(msg) return m.handleDashboardKey(msg)
} }
return m, nil return m, nil
@@ -521,33 +506,27 @@ func (m *Model) handleAlertDetailKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
} }
func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg.String() { switch msg.String() {
case "q": case "q":
return m, tea.Quit return m, tea.Quit
case "/": case "/":
if m.currentTab == 0 { if m.currentTab == tabMonitors {
m.filterMode = true m.filterMode = true
m.recalcLayout() m.recalcLayout()
return m, nil return m, nil
} }
case "f":
if m.state == stateLogs {
m.logFilterImportant = !m.logFilterImportant
m.refreshLogContent()
return m, nil
}
case "tab": case "tab":
m.switchTab(m.currentTab + 1) m.switchTab(m.currentTab + 1)
case "pgup", "pgdown": case "left", "h":
if m.state == stateLogs { if m.currentTab == tabSettings {
m.logViewport, cmd = m.logViewport.Update(msg) m.switchSettingsSection(m.settingsSection - 1)
return m, cmd }
case "right", "l":
if m.currentTab == tabSettings {
m.switchSettingsSection(m.settingsSection + 1)
} }
case "up", "k": case "up", "k":
if m.state == stateLogs { if m.cursor > 0 {
m.logViewport.ScrollUp(1)
} else if m.cursor > 0 {
m.cursor-- m.cursor--
if m.cursor < m.tableOffset { if m.cursor < m.tableOffset {
m.tableOffset = m.cursor m.tableOffset = m.cursor
@@ -555,9 +534,6 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.syncSelectedID() m.syncSelectedID()
} }
case "down", "j": case "down", "j":
if m.state == stateLogs {
m.logViewport.ScrollDown(1)
} else {
max := m.currentListLen() - 1 max := m.currentListLen() - 1
if m.cursor < max { if m.cursor < max {
m.cursor++ m.cursor++
@@ -566,18 +542,17 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
} }
m.syncSelectedID() m.syncSelectedID()
} }
}
case "n": case "n":
return m.handleNewItem() return m.handleNewItem()
case "e", "enter": case "e", "enter":
return m.handleEditItem() return m.handleEditItem()
case "t": case "t":
if m.currentTab == 1 && len(m.alerts) > 0 { if m.currentTab == tabSettings && m.settingsSection == sectionAlerts && len(m.alerts) > 0 {
a := m.alerts[m.cursor] a := m.alerts[m.cursor]
return m, m.testAlertCmd(a.ID, a.Name) return m, m.testAlertCmd(a.ID, a.Name)
} }
case " ": case " ":
if m.currentTab == 0 && len(m.sites) > 0 && m.sites[m.cursor].Type == "group" { if m.currentTab == tabMonitors && len(m.sites) > 0 && m.sites[m.cursor].Type == "group" {
gid := m.sites[m.cursor].ID gid := m.sites[m.cursor].ID
m.collapsed[gid] = !m.collapsed[gid] m.collapsed[gid] = !m.collapsed[gid]
payload := collapsedJSON(m.collapsed) payload := collapsedJSON(m.collapsed)
@@ -588,7 +563,7 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
}) })
} }
case "p": case "p":
if m.currentTab == 0 && len(m.sites) > 0 { if m.currentTab == tabMonitors && len(m.sites) > 0 {
id := m.sites[m.cursor].ID id := m.sites[m.cursor].ID
paused := m.engine.ToggleSitePause(id) paused := m.engine.ToggleSitePause(id)
st := m.store st := m.store
@@ -598,14 +573,14 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
}) })
} }
case "i": case "i":
if m.currentTab == 0 && len(m.sites) > 0 { if m.currentTab == tabMonitors && len(m.sites) > 0 {
m.state = stateDetail m.state = stateDetail
return m, m.loadDetailCmd(m.sites[m.cursor].ID) return m, m.loadDetailCmd(m.sites[m.cursor].ID)
} else if m.currentTab == 1 && len(m.alerts) > 0 { } else if m.currentTab == tabSettings && m.settingsSection == sectionAlerts && len(m.alerts) > 0 {
m.state = stateAlertDetail m.state = stateAlertDetail
} }
case "x": case "x":
if m.currentTab == 4 && len(m.maintenanceWindows) > 0 { if m.currentTab == tabMaint && len(m.maintenanceWindows) > 0 {
mw := m.maintenanceWindows[m.cursor] mw := m.maintenanceWindows[m.cursor]
now := time.Now() now := time.Now()
isActive := !mw.StartTime.After(now) && (mw.EndTime.IsZero() || mw.EndTime.After(now)) isActive := !mw.StartTime.After(now) && (mw.EndTime.IsZero() || mw.EndTime.After(now))
@@ -637,88 +612,94 @@ func (m *Model) handleNewItem() (tea.Model, tea.Cmd) {
m.editID = 0 m.editID = 0
m.editToken = "" m.editToken = ""
switch m.currentTab { switch m.currentTab {
case 0: case tabMonitors:
m.state = stateFormSite m.state = stateFormSite
return m, m.initSiteHuhForm() return m, m.initSiteHuhForm()
case 1: case tabMaint:
m.state = stateFormAlert
return m, m.initAlertHuhForm()
case 4:
m.state = stateFormMaint m.state = stateFormMaint
return m, m.initMaintHuhForm() return m, m.initMaintHuhForm()
case 5: case tabSettings:
switch m.settingsSection {
case sectionAlerts:
m.state = stateFormAlert
return m, m.initAlertHuhForm()
case sectionUsers:
if m.isAdmin { if m.isAdmin {
m.state = stateFormUser m.state = stateFormUser
return m, m.initUserHuhForm() return m, m.initUserHuhForm()
} }
} }
}
return m, nil return m, nil
} }
func (m *Model) handleEditItem() (tea.Model, tea.Cmd) { func (m *Model) handleEditItem() (tea.Model, tea.Cmd) {
switch m.currentTab { switch m.currentTab {
case 0: case tabMonitors:
if len(m.sites) > 0 { if len(m.sites) > 0 {
m.editID = m.sites[m.cursor].ID m.editID = m.sites[m.cursor].ID
m.editToken = m.sites[m.cursor].Token m.editToken = m.sites[m.cursor].Token
m.state = stateFormSite m.state = stateFormSite
return m, m.initSiteHuhForm() return m, m.initSiteHuhForm()
} }
case 1: case tabSettings:
switch m.settingsSection {
case sectionAlerts:
if len(m.alerts) > 0 { if len(m.alerts) > 0 {
m.editID = m.alerts[m.cursor].ID m.editID = m.alerts[m.cursor].ID
m.state = stateFormAlert m.state = stateFormAlert
return m, m.initAlertHuhForm() return m, m.initAlertHuhForm()
} }
case 5: case sectionUsers:
if m.isAdmin && len(m.users) > 0 { if m.isAdmin && len(m.users) > 0 {
m.editID = m.users[m.cursor].ID m.editID = m.users[m.cursor].ID
m.state = stateFormUser m.state = stateFormUser
return m, m.initUserHuhForm() return m, m.initUserHuhForm()
} }
} }
}
return m, nil return m, nil
} }
func (m *Model) handleDeleteItem() (tea.Model, tea.Cmd) { func (m *Model) handleDeleteItem() (tea.Model, tea.Cmd) {
switch m.currentTab { switch m.currentTab {
case 0: case tabMonitors:
if len(m.sites) > 0 { if len(m.sites) > 0 {
m.deleteID = m.sites[m.cursor].ID m.deleteID = m.sites[m.cursor].ID
m.deleteName = m.sites[m.cursor].Name m.deleteName = m.sites[m.cursor].Name
m.deleteTab = 0 m.deleteTab = tabMonitors
m.state = stateConfirmDelete m.state = stateConfirmDelete
} }
case 1: case tabMaint:
if len(m.alerts) > 0 {
m.deleteID = m.alerts[m.cursor].ID
m.deleteName = m.alerts[m.cursor].Name
m.deleteTab = 1
m.state = stateConfirmDelete
}
case 4:
if len(m.maintenanceWindows) > 0 { if len(m.maintenanceWindows) > 0 {
m.deleteID = m.maintenanceWindows[m.cursor].ID m.deleteID = m.maintenanceWindows[m.cursor].ID
m.deleteName = m.maintenanceWindows[m.cursor].Title m.deleteName = m.maintenanceWindows[m.cursor].Title
m.deleteTab = 4 m.deleteTab = tabMaint
m.state = stateConfirmDelete m.state = stateConfirmDelete
} }
case 5: case tabSettings:
switch m.settingsSection {
case sectionAlerts:
if len(m.alerts) > 0 {
m.deleteID = m.alerts[m.cursor].ID
m.deleteName = m.alerts[m.cursor].Name
m.deleteTab = tabSettings
m.state = stateConfirmDelete
}
case sectionUsers:
if m.isAdmin && len(m.users) > 0 { if m.isAdmin && len(m.users) > 0 {
m.deleteID = m.users[m.cursor].ID m.deleteID = m.users[m.cursor].ID
m.deleteName = m.users[m.cursor].Username m.deleteName = m.users[m.cursor].Username
m.deleteTab = 5 m.deleteTab = tabSettings
m.state = stateConfirmDelete m.state = stateConfirmDelete
} }
} }
}
return m, nil return m, nil
} }
func (m *Model) handleClick(msg tea.MouseMsg) (tea.Model, tea.Cmd) { func (m *Model) handleClick(msg tea.MouseMsg) (tea.Model, tea.Cmd) {
tabCount := 5 tabCount := tabSettings + 1
if m.isAdmin {
tabCount = 6
}
for i := 0; i < tabCount; i++ { for i := 0; i < tabCount; i++ {
if m.zones.Get(fmt.Sprintf("tab-%d", i)).InBounds(msg) { if m.zones.Get(fmt.Sprintf("tab-%d", i)).InBounds(msg) {
m.switchTab(i) m.switchTab(i)
@@ -743,24 +724,14 @@ func (m *Model) handleClick(msg tea.MouseMsg) (tea.Model, tea.Cmd) {
} }
func (m *Model) switchTab(idx int) { func (m *Model) switchTab(idx int) {
maxTabs := 4 maxTabs := tabSettings
if m.isAdmin {
maxTabs = 5
}
if idx > maxTabs { if idx > maxTabs {
idx = 0 idx = 0
} }
m.currentTab = idx m.currentTab = idx
m.cursor = 0 m.cursor = 0
m.tableOffset = 0 m.tableOffset = 0
switch idx {
case 2:
m.state = stateLogs
case 5:
m.state = stateUsers
default:
m.state = stateDashboard m.state = stateDashboard
}
} }
func (m *Model) adjustCursor(_ int) { func (m *Model) adjustCursor(_ int) {
@@ -791,30 +762,36 @@ func (m *Model) submitForm() tea.Cmd {
func (m Model) currentListLen() int { func (m Model) currentListLen() int {
switch m.currentTab { switch m.currentTab {
case 1: case tabMonitors:
return len(m.alerts)
case 3:
return len(m.nodes)
case 4:
return len(m.maintenanceWindows)
case 5:
return len(m.users)
default:
return len(m.sites) return len(m.sites)
case tabMaint:
return len(m.maintenanceWindows)
case tabSettings:
switch m.settingsSection {
case sectionAlerts:
return len(m.alerts)
case sectionNodes:
return len(m.nodes)
case sectionUsers:
return len(m.users)
} }
}
return 0
} }
func (m Model) currentZonePrefix() (string, int) { func (m Model) currentZonePrefix() (string, int) {
switch m.currentTab { switch m.currentTab {
case 0: case tabMonitors:
return "site", len(m.sites) return "site", len(m.sites)
case 1: case tabMaint:
return "alert", len(m.alerts)
case 4:
return "maint", len(m.maintenanceWindows) return "maint", len(m.maintenanceWindows)
case 5: case tabSettings:
switch m.settingsSection {
case sectionAlerts:
return "alert", len(m.alerts)
case sectionUsers:
return "user", len(m.users) return "user", len(m.users)
default:
return "site", 0
} }
}
return "site", 0
} }
+33 -28
View File
@@ -151,20 +151,22 @@ func (m Model) viewDashboard() string {
var content string var content string
switch m.currentTab { switch m.currentTab {
case 0: case tabMonitors:
content = m.viewSitesTab() monitors := m.viewSitesTab()
case 1: if m.termWidth >= wideBreakpoint {
content = m.viewAlertsTab() availW := m.termWidth - chromePadH
case 2: leftW := availW * 70 / 100
content = m.viewLogsTab() rightW := availW - leftW
case 3: left := lipgloss.NewStyle().Width(leftW).Render(monitors)
content = m.viewNodesTab() right := lipgloss.NewStyle().Width(rightW).Render(m.viewLogsTab())
case 4: content = lipgloss.JoinHorizontal(lipgloss.Top, left, right)
content = m.viewMaintTab() } else {
case 5: content = monitors
if m.isAdmin {
content = m.viewUsersTab()
} }
case tabMaint:
content = m.viewMaintTab()
case tabSettings:
content = m.viewSettingsTab()
} }
content = strings.TrimSpace(content) content = strings.TrimSpace(content)
@@ -199,15 +201,15 @@ type tabEntry struct {
} }
func (m Model) renderTabBar(stats dashboardStats) string { func (m Model) renderTabBar(stats dashboardStats) string {
settingsCount := len(m.alerts) + len(m.nodes)
settingsWarn := stats.offlineNodes
if m.isAdmin {
settingsCount += len(m.users)
}
tabs := []tabEntry{ tabs := []tabEntry{
{"Monitors", stats.totalMonitors, stats.downCount + stats.lateCount}, {"Monitors", stats.totalMonitors, stats.downCount + stats.lateCount},
{"Alerts", len(m.alerts), 0},
{"Logs", 0, 0},
{"Nodes", len(m.nodes), stats.offlineNodes},
{"Maint", len(m.maintenanceWindows), stats.activeMaint}, {"Maint", len(m.maintenanceWindows), stats.activeMaint},
} {"Settings", settingsCount, settingsWarn},
if m.isAdmin {
tabs = append(tabs, tabEntry{"Users", len(m.users), 0})
} }
countStyle := lipgloss.NewStyle().Foreground(m.theme.Muted) countStyle := lipgloss.NewStyle().Foreground(m.theme.Muted)
@@ -270,23 +272,26 @@ func (m Model) renderFooter(stats dashboardStats) string {
var keys string var keys string
switch m.currentTab { switch m.currentTab {
case 0: case tabMonitors:
keys = "[/]Filter [n]New [e]Edit [i]Info [d]Del [p]Pause [Space]Collapse [T]Theme [Tab]Switch [q]Quit" keys = "[/]Filter [n]New [e]Edit [i]Info [d]Del [p]Pause [Space]Collapse [T]Theme [Tab]Switch [q]Quit"
case 1: case tabMaint:
keys = "[n]New [e]Edit [i]Info [d]Del [t]Test [T]Theme [Tab]Switch [q]Quit"
case 2:
keys = "[↑/↓]Scroll [PgUp/PgDn]Page [f]Filter [T]Theme [Tab]Switch [q]Quit"
case 4:
keys = "[n]New [x]End [d]Del [T]Theme [Tab]Switch [q]Quit" keys = "[n]New [x]End [d]Del [T]Theme [Tab]Switch [q]Quit"
case 5: case tabSettings:
keys = "[n]Add [d]Revoke [T]Theme [Tab]Switch [q]Quit" switch m.settingsSection {
case sectionAlerts:
keys = "[n]New [e]Edit [i]Info [d]Del [t]Test [←/→]Section [T]Theme [Tab]Switch [q]Quit"
case sectionUsers:
keys = "[n]Add [d]Revoke [←/→]Section [T]Theme [Tab]Switch [q]Quit"
default:
keys = "[←/→]Section [T]Theme [Tab]Switch [q]Quit"
}
default: default:
keys = "[T]Theme [Tab]Switch [q]Quit" keys = "[T]Theme [Tab]Switch [q]Quit"
} }
ver := m.st.subtleStyle.Render("v" + m.version) ver := m.st.subtleStyle.Render("v" + m.version)
line := statusLine + " " + m.st.subtleStyle.Render(keys) + " " + ver line := statusLine + " " + m.st.subtleStyle.Render(keys) + " " + ver
if m.filterText != "" && m.currentTab == 0 { if m.filterText != "" && m.currentTab == tabMonitors {
line = m.st.subtleStyle.Render(fmt.Sprintf("filter: %s", m.filterText)) + " " + statusLine + " " + m.st.subtleStyle.Render(keys) + " " + ver line = m.st.subtleStyle.Render(fmt.Sprintf("filter: %s", m.filterText)) + " " + statusLine + " " + m.st.subtleStyle.Render(keys) + " " + ver
} }