feat(tui): move detail panel from bottom overlay to right side
Restructure dashboard from [Monitors 70% | Logs 30%] to [Monitors 45% | Detail 55%] master-detail layout. Detail panel now sits beside the monitor list instead of below it, keeping dashboard context visible during inspection. - Detail panel renders in horizontal split (right of monitors) - Logs sidebar removed; `l` key opens fullscreen logs directly - recalcLayout no longer subtracts detail height from table rows - Remove unused renderCompactLogLine/viewLogsSidebar (Phase 4 recreates)
This commit is contained in:
@@ -2,76 +2,8 @@ package tui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.lerkolabs.com/lerkolabs/uptop/internal/models"
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m Model) renderCompactLogLine(entry models.LogEntry, maxW int) string {
|
|
||||||
sev := classifyLog(entry.Message)
|
|
||||||
|
|
||||||
var tag string
|
|
||||||
switch sev {
|
|
||||||
case severityDown:
|
|
||||||
tag = m.st.dangerStyle.Render("▼")
|
|
||||||
case severityUp:
|
|
||||||
tag = m.st.specialStyle.Render("▲")
|
|
||||||
case severityWarn:
|
|
||||||
tag = m.st.warnStyle.Render("◆")
|
|
||||||
case severitySystem:
|
|
||||||
tag = m.st.titleStyle.Render("●")
|
|
||||||
default:
|
|
||||||
tag = m.st.subtleStyle.Render("·")
|
|
||||||
}
|
|
||||||
|
|
||||||
ts := entry.CreatedAt.Local().Format("01/02 15:04")
|
|
||||||
|
|
||||||
msg := entry.Message
|
|
||||||
msg = strings.TrimPrefix(msg, "Monitor ")
|
|
||||||
msg = strings.TrimPrefix(msg, "Push ")
|
|
||||||
|
|
||||||
prefixW := len(ts) + 4
|
|
||||||
msgW := maxW - prefixW
|
|
||||||
if msgW < 5 {
|
|
||||||
msgW = 5
|
|
||||||
}
|
|
||||||
msg = limitStr(msg, msgW)
|
|
||||||
|
|
||||||
return " " + m.st.subtleStyle.Render(ts) + " " + tag + " " + msg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) viewLogsSidebar(width, maxLines int) string {
|
|
||||||
logs := m.engine.GetLogs()
|
|
||||||
if len(logs) == 0 {
|
|
||||||
return m.st.subtleStyle.Render(" No logs yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
sidebarStyle := lipgloss.NewStyle().Width(width).MaxWidth(width)
|
|
||||||
|
|
||||||
var all []string
|
|
||||||
for _, entry := range logs {
|
|
||||||
if strings.TrimSpace(entry.Message) == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if m.logFilterImportant && !isImportantLog(classifyLog(entry.Message)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
all = append(all, m.renderCompactLogLine(entry, width))
|
|
||||||
}
|
|
||||||
|
|
||||||
start := m.logScrollOffset
|
|
||||||
if start > len(all) {
|
|
||||||
start = len(all)
|
|
||||||
}
|
|
||||||
end := start + maxLines
|
|
||||||
if end > len(all) {
|
|
||||||
end = len(all)
|
|
||||||
}
|
|
||||||
visible := all[start:end]
|
|
||||||
|
|
||||||
return sidebarStyle.Render(strings.Join(visible, "\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) scrollLogs(delta int) {
|
func (m *Model) scrollLogs(delta int) {
|
||||||
logs := m.engine.GetLogs()
|
logs := m.engine.GetLogs()
|
||||||
total := 0
|
total := 0
|
||||||
|
|||||||
+5
-14
@@ -155,16 +155,11 @@ func (m *Model) handleFormMsg(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const detailInlineHeight = 12
|
|
||||||
|
|
||||||
func (m *Model) recalcLayout() {
|
func (m *Model) recalcLayout() {
|
||||||
chrome := chromeBase
|
chrome := chromeBase
|
||||||
if m.filterMode || m.filterText != "" {
|
if m.filterMode || m.filterText != "" {
|
||||||
chrome++
|
chrome++
|
||||||
}
|
}
|
||||||
if m.detailOpen {
|
|
||||||
chrome += detailInlineHeight
|
|
||||||
}
|
|
||||||
m.maxTableRows = m.termHeight - chrome
|
m.maxTableRows = m.termHeight - chrome
|
||||||
if m.maxTableRows < 1 {
|
if m.maxTableRows < 1 {
|
||||||
m.maxTableRows = 1
|
m.maxTableRows = 1
|
||||||
@@ -259,7 +254,7 @@ func (m *Model) handleLogsFullscreen(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
case "esc", "q":
|
case "esc", "q":
|
||||||
m.state = stateDashboard
|
m.state = stateDashboard
|
||||||
m.focusedPanel = panelLogs
|
m.focusedPanel = panelMonitors
|
||||||
case "ctrl+c":
|
case "ctrl+c":
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
case "f":
|
case "f":
|
||||||
@@ -756,14 +751,10 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|||||||
m.recalcLayout()
|
m.recalcLayout()
|
||||||
}
|
}
|
||||||
case "l":
|
case "l":
|
||||||
if m.focusedPanel == panelLogs {
|
m.refreshLogContent()
|
||||||
m.logsOpen = false
|
m.logViewport.GotoTop()
|
||||||
m.focusedPanel = panelMonitors
|
m.state = stateLogs
|
||||||
} else {
|
return m, nil
|
||||||
m.logsOpen = true
|
|
||||||
m.focusedPanel = panelLogs
|
|
||||||
}
|
|
||||||
m.recalcLayout()
|
|
||||||
case "up", "k":
|
case "up", "k":
|
||||||
if m.focusedPanel == panelMaint {
|
if m.focusedPanel == panelMaint {
|
||||||
m.scrollMaintCursor(-1)
|
m.scrollMaintCursor(-1)
|
||||||
|
|||||||
@@ -156,9 +156,9 @@ func (m Model) viewMonitorsLayout() string {
|
|||||||
wide := m.termWidth >= wideBreakpoint
|
wide := m.termWidth >= wideBreakpoint
|
||||||
|
|
||||||
showMaint := m.maintOpen && wide
|
showMaint := m.maintOpen && wide
|
||||||
showLogs := m.logsOpen && wide
|
showDetail := m.detailOpen && wide
|
||||||
|
|
||||||
var maintW, logsW, monW int
|
var maintW, detailW, monW int
|
||||||
if showMaint {
|
if showMaint {
|
||||||
maintW = maintSidebarW
|
maintW = maintSidebarW
|
||||||
if maintW > availW/4 {
|
if maintW > availW/4 {
|
||||||
@@ -166,9 +166,9 @@ func (m Model) viewMonitorsLayout() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
remaining := availW - maintW
|
remaining := availW - maintW
|
||||||
if showLogs {
|
if showDetail {
|
||||||
monW = remaining * 70 / 100
|
monW = remaining * 45 / 100
|
||||||
logsW = remaining - monW
|
detailW = remaining - monW
|
||||||
} else {
|
} else {
|
||||||
monW = remaining
|
monW = remaining
|
||||||
}
|
}
|
||||||
@@ -185,24 +185,17 @@ func (m Model) viewMonitorsLayout() string {
|
|||||||
topParts = append(topParts, maintPanel)
|
topParts = append(topParts, maintPanel)
|
||||||
}
|
}
|
||||||
topParts = append(topParts, monPanel)
|
topParts = append(topParts, monPanel)
|
||||||
if showLogs {
|
if showDetail {
|
||||||
logContent := m.viewLogsSidebar(logsW-2, m.maxTableRows)
|
|
||||||
logPanel := m.zones.Mark("panel-logs", m.titledPanel("Logs", logContent, logsW, m.focusedPanel == panelLogs))
|
|
||||||
topParts = append(topParts, logPanel)
|
|
||||||
}
|
|
||||||
|
|
||||||
top := lipgloss.JoinHorizontal(lipgloss.Top, topParts...)
|
|
||||||
|
|
||||||
if m.detailOpen {
|
|
||||||
site := ""
|
site := ""
|
||||||
if m.cursor < len(m.sites) {
|
if m.cursor < len(m.sites) {
|
||||||
site = m.sites[m.cursor].Name
|
site = m.sites[m.cursor].Name
|
||||||
}
|
}
|
||||||
detail := m.viewDetailInline(availW - 2)
|
detail := m.viewDetailInline(detailW - 2)
|
||||||
detailPanel := m.zones.Mark("panel-detail", m.titledPanel(site, detail, availW, m.focusedPanel == panelDetail))
|
detailPanel := m.zones.Mark("panel-detail", m.titledPanel(site, detail, detailW, m.focusedPanel == panelDetail))
|
||||||
return top + "\n" + detailPanel
|
topParts = append(topParts, detailPanel)
|
||||||
}
|
}
|
||||||
return top
|
|
||||||
|
return lipgloss.JoinHorizontal(lipgloss.Top, topParts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) viewDashboard() string {
|
func (m Model) viewDashboard() string {
|
||||||
@@ -286,8 +279,6 @@ func (m Model) renderFooter(_ dashboardStats) string {
|
|||||||
var keys string
|
var keys string
|
||||||
if m.focusedPanel == panelMaint {
|
if m.focusedPanel == panelMaint {
|
||||||
keys = "[n]New [x]End [d]Del [Esc]Back [S]Settings [T]Theme [q]Quit"
|
keys = "[n]New [x]End [d]Del [Esc]Back [S]Settings [T]Theme [q]Quit"
|
||||||
} else if m.focusedPanel == panelLogs {
|
|
||||||
keys = "[↑/↓]Scroll [Enter]Expand [l/Esc]Back [S]Settings [T]Theme [q]Quit"
|
|
||||||
} else if m.detailOpen {
|
} else if m.detailOpen {
|
||||||
keys = "[i]Close [Enter]Expand [h]History [s]SLA [e]Edit [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit"
|
keys = "[i]Close [Enter]Expand [h]History [s]SLA [e]Edit [m]Maint [l]Logs [S]Settings [T]Theme [q]Quit"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user