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:
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
+23
-10
@@ -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 (
|
||||||
@@ -102,16 +114,17 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
state sessionState
|
state sessionState
|
||||||
currentTab int
|
currentTab int
|
||||||
cursor int
|
settingsSection int
|
||||||
selectedID int
|
cursor int
|
||||||
tableOffset int
|
selectedID int
|
||||||
maxTableRows int
|
tableOffset int
|
||||||
termWidth int
|
maxTableRows int
|
||||||
termHeight int
|
termWidth int
|
||||||
editID int
|
termHeight int
|
||||||
editToken string
|
editID int
|
||||||
|
editToken string
|
||||||
|
|
||||||
huhForm *huh.Form
|
huhForm *huh.Form
|
||||||
siteFormData *siteFormData
|
siteFormData *siteFormData
|
||||||
|
|||||||
+107
-130
@@ -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:
|
||||||
cmd = writeCmd("Delete user", func() error { return st.DeleteUser(context.Background(), id) })
|
switch m.settingsSection {
|
||||||
m.adjustCursor(len(m.users) - 1)
|
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) })
|
||||||
|
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,29 +534,25 @@ 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 {
|
max := m.currentListLen() - 1
|
||||||
m.logViewport.ScrollDown(1)
|
if m.cursor < max {
|
||||||
} else {
|
m.cursor++
|
||||||
max := m.currentListLen() - 1
|
if m.cursor >= m.tableOffset+m.maxTableRows {
|
||||||
if m.cursor < max {
|
m.tableOffset++
|
||||||
m.cursor++
|
|
||||||
if m.cursor >= m.tableOffset+m.maxTableRows {
|
|
||||||
m.tableOffset++
|
|
||||||
}
|
|
||||||
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,19 +612,22 @@ 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:
|
||||||
if m.isAdmin {
|
switch m.settingsSection {
|
||||||
m.state = stateFormUser
|
case sectionAlerts:
|
||||||
return m, m.initUserHuhForm()
|
m.state = stateFormAlert
|
||||||
|
return m, m.initAlertHuhForm()
|
||||||
|
case sectionUsers:
|
||||||
|
if m.isAdmin {
|
||||||
|
m.state = stateFormUser
|
||||||
|
return m, m.initUserHuhForm()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
@@ -657,24 +635,27 @@ func (m *Model) handleNewItem() (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
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:
|
||||||
if len(m.alerts) > 0 {
|
switch m.settingsSection {
|
||||||
m.editID = m.alerts[m.cursor].ID
|
case sectionAlerts:
|
||||||
m.state = stateFormAlert
|
if len(m.alerts) > 0 {
|
||||||
return m, m.initAlertHuhForm()
|
m.editID = m.alerts[m.cursor].ID
|
||||||
}
|
m.state = stateFormAlert
|
||||||
case 5:
|
return m, m.initAlertHuhForm()
|
||||||
if m.isAdmin && len(m.users) > 0 {
|
}
|
||||||
m.editID = m.users[m.cursor].ID
|
case sectionUsers:
|
||||||
m.state = stateFormUser
|
if m.isAdmin && len(m.users) > 0 {
|
||||||
return m, m.initUserHuhForm()
|
m.editID = m.users[m.cursor].ID
|
||||||
|
m.state = stateFormUser
|
||||||
|
return m, m.initUserHuhForm()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
@@ -682,43 +663,43 @@ func (m *Model) handleEditItem() (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
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:
|
||||||
if m.isAdmin && len(m.users) > 0 {
|
switch m.settingsSection {
|
||||||
m.deleteID = m.users[m.cursor].ID
|
case sectionAlerts:
|
||||||
m.deleteName = m.users[m.cursor].Username
|
if len(m.alerts) > 0 {
|
||||||
m.deleteTab = 5
|
m.deleteID = m.alerts[m.cursor].ID
|
||||||
m.state = stateConfirmDelete
|
m.deleteName = m.alerts[m.cursor].Name
|
||||||
|
m.deleteTab = tabSettings
|
||||||
|
m.state = stateConfirmDelete
|
||||||
|
}
|
||||||
|
case sectionUsers:
|
||||||
|
if m.isAdmin && len(m.users) > 0 {
|
||||||
|
m.deleteID = m.users[m.cursor].ID
|
||||||
|
m.deleteName = m.users[m.cursor].Username
|
||||||
|
m.deleteTab = tabSettings
|
||||||
|
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 {
|
m.state = stateDashboard
|
||||||
case 2:
|
|
||||||
m.state = stateLogs
|
|
||||||
case 5:
|
|
||||||
m.state = stateUsers
|
|
||||||
default:
|
|
||||||
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:
|
||||||
return "user", len(m.users)
|
switch m.settingsSection {
|
||||||
default:
|
case sectionAlerts:
|
||||||
return "site", 0
|
return "alert", len(m.alerts)
|
||||||
|
case sectionUsers:
|
||||||
|
return "user", len(m.users)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return "site", 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user