feat(tui,status): add per-site pause, fix viewport, polish status page
Per-site pause: [p] key toggles pause for selected monitor in TUI. Paused monitors skip checks, persist to DB, show on status page. Status page: replace full-page reload with fetch-based DOM updates to eliminate scroll-jump on refresh. Add summary bar (UP/DOWN/PAUSED counts), stale-data indicator, and fix SSL EXP CSS class bug. TUI: constrain tables to terminal width via lipgloss .Width() to prevent row wrapping that pushed header off-screen. Add MaxHeight safety net. Bump subtle style from #383838 to #565f89 for readability on dark terminals.
This commit is contained in:
@@ -26,8 +26,6 @@ var (
|
||||
|
||||
alertBorderStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#444"))
|
||||
|
||||
alertColWidths = []int{4, 16, 10, 36}
|
||||
)
|
||||
|
||||
type alertFormData struct {
|
||||
@@ -120,27 +118,25 @@ func (m Model) viewAlertsTab() string {
|
||||
})
|
||||
}
|
||||
|
||||
tableWidth := m.termWidth - 6
|
||||
if tableWidth < 40 {
|
||||
tableWidth = 40
|
||||
}
|
||||
|
||||
t := table.New().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderStyle(alertBorderStyle).
|
||||
Width(tableWidth).
|
||||
Headers("ID", "NAME", "TYPE", "CONFIG").
|
||||
Rows(rows...).
|
||||
StyleFunc(func(row, col int) lipgloss.Style {
|
||||
if row == table.HeaderRow {
|
||||
s := alertHeaderStyle
|
||||
if col < len(alertColWidths) {
|
||||
s = s.Width(alertColWidths[col])
|
||||
}
|
||||
return s
|
||||
return alertHeaderStyle
|
||||
}
|
||||
s := alertCellStyle
|
||||
if row == selectedVisual {
|
||||
s = alertSelectedStyle
|
||||
return alertSelectedStyle
|
||||
}
|
||||
if col < len(alertColWidths) {
|
||||
s = s.Width(alertColWidths[col])
|
||||
}
|
||||
return s
|
||||
return alertCellStyle
|
||||
})
|
||||
|
||||
return "\n" + t.Render()
|
||||
|
||||
+14
-15
@@ -34,8 +34,6 @@ var (
|
||||
|
||||
siteBorderStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#444"))
|
||||
|
||||
siteColWidths = []int{4, 14, 6, 8, 9, 8, 20, 10, 6}
|
||||
)
|
||||
|
||||
type siteFormData struct {
|
||||
@@ -195,7 +193,10 @@ func fmtRetries(site models.Site) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func fmtStatus(status string) string {
|
||||
func fmtStatus(status string, paused bool) string {
|
||||
if paused {
|
||||
return warnStyle.Render("PAUSED")
|
||||
}
|
||||
switch {
|
||||
case status == "DOWN" || status == "SSL EXP":
|
||||
return dangerStyle.Render(status)
|
||||
@@ -236,7 +237,7 @@ func (m Model) viewSitesTab() string {
|
||||
strconv.Itoa(site.ID),
|
||||
m.zones.Mark(fmt.Sprintf("site-%d", i), limitStr(site.Name, 13)),
|
||||
site.Type,
|
||||
fmtStatus(site.Status),
|
||||
fmtStatus(site.Status, site.Paused),
|
||||
fmtLatency(site.Latency),
|
||||
fmtUptime(hist.TotalChecks, hist.UpChecks),
|
||||
spark,
|
||||
@@ -245,27 +246,25 @@ func (m Model) viewSitesTab() string {
|
||||
})
|
||||
}
|
||||
|
||||
tableWidth := m.termWidth - 6
|
||||
if tableWidth < 40 {
|
||||
tableWidth = 40
|
||||
}
|
||||
|
||||
t := table.New().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderStyle(siteBorderStyle).
|
||||
Width(tableWidth).
|
||||
Headers("ID", "NAME", "TYPE", "STATUS", "LATENCY", "UPTIME", "HISTORY", "SSL", "RETRY").
|
||||
Rows(rows...).
|
||||
StyleFunc(func(row, col int) lipgloss.Style {
|
||||
if row == table.HeaderRow {
|
||||
s := siteHeaderStyle
|
||||
if col < len(siteColWidths) {
|
||||
s = s.Width(siteColWidths[col])
|
||||
}
|
||||
return s
|
||||
return siteHeaderStyle
|
||||
}
|
||||
s := siteCellStyle
|
||||
if row == selectedVisual {
|
||||
s = siteSelectedStyle
|
||||
return siteSelectedStyle
|
||||
}
|
||||
if col < len(siteColWidths) {
|
||||
s = s.Width(siteColWidths[col])
|
||||
}
|
||||
return s
|
||||
return siteCellStyle
|
||||
})
|
||||
|
||||
return "\n" + t.Render()
|
||||
|
||||
@@ -26,8 +26,6 @@ var (
|
||||
|
||||
userBorderStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#444"))
|
||||
|
||||
userColWidths = []int{4, 16, 10, 44}
|
||||
)
|
||||
|
||||
type userFormData struct {
|
||||
@@ -73,27 +71,25 @@ func (m Model) viewUsersTab() string {
|
||||
})
|
||||
}
|
||||
|
||||
tableWidth := m.termWidth - 6
|
||||
if tableWidth < 40 {
|
||||
tableWidth = 40
|
||||
}
|
||||
|
||||
t := table.New().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderStyle(userBorderStyle).
|
||||
Width(tableWidth).
|
||||
Headers("ID", "USERNAME", "ROLE", "PUBLIC KEY").
|
||||
Rows(rows...).
|
||||
StyleFunc(func(row, col int) lipgloss.Style {
|
||||
if row == table.HeaderRow {
|
||||
s := userHeaderStyle
|
||||
if col < len(userColWidths) {
|
||||
s = s.Width(userColWidths[col])
|
||||
}
|
||||
return s
|
||||
return userHeaderStyle
|
||||
}
|
||||
s := userCellStyle
|
||||
if row == selectedVisual {
|
||||
s = userSelectedStyle
|
||||
return userSelectedStyle
|
||||
}
|
||||
if col < len(userColWidths) {
|
||||
s = s.Width(userColWidths[col])
|
||||
}
|
||||
return s
|
||||
return userCellStyle
|
||||
})
|
||||
|
||||
return "\n" + t.Render()
|
||||
|
||||
+21
-3
@@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
subtleStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"})
|
||||
subtleStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#9ca0b0", Dark: "#565f89"})
|
||||
specialStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"})
|
||||
warnStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#F0E442", Dark: "#F0E442"})
|
||||
dangerStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#F25D94", Dark: "#F25D94"})
|
||||
@@ -48,6 +48,8 @@ type Model struct {
|
||||
cursor int
|
||||
tableOffset int
|
||||
maxTableRows int
|
||||
termWidth int
|
||||
termHeight int
|
||||
editID int
|
||||
editToken string
|
||||
|
||||
@@ -126,6 +128,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.termWidth = msg.Width
|
||||
m.termHeight = msg.Height
|
||||
m.maxTableRows = msg.Height - 12
|
||||
if m.maxTableRows < 1 {
|
||||
m.maxTableRows = 1
|
||||
@@ -255,6 +259,16 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.state = stateFormUser
|
||||
return m, m.initUserHuhForm()
|
||||
}
|
||||
case "p":
|
||||
if m.currentTab == 0 && len(m.sites) > 0 {
|
||||
site := m.sites[m.cursor]
|
||||
monitor.ToggleSitePause(site.ID)
|
||||
site.Paused = !site.Paused
|
||||
if store.Get() != nil {
|
||||
store.Get().UpdateSitePaused(site.ID, site.Paused)
|
||||
}
|
||||
m.refreshData()
|
||||
}
|
||||
case "d", "backspace":
|
||||
if m.currentTab == 1 && len(m.alerts) > 0 {
|
||||
store.Get().DeleteAlert(m.alerts[m.cursor].ID)
|
||||
@@ -476,11 +490,15 @@ func (m Model) viewDashboard() string {
|
||||
}
|
||||
}
|
||||
|
||||
footer := subtleStyle.Render("\n[n] New [e/Enter] Edit [d] Delete [Tab/Click] Switch [Ctrl+L] Clear [q] Quit")
|
||||
footer := subtleStyle.Render("\n[n] New [e/Enter] Edit [d] Delete [p] Pause [Tab/Click] Switch [Ctrl+L] Clear [q] Quit")
|
||||
if m.currentTab == 3 {
|
||||
footer = subtleStyle.Render("\n[n] Add User [d] Revoke [Tab/Click] Switch [Ctrl+L] Clear [q] Quit")
|
||||
}
|
||||
return lipgloss.NewStyle().Padding(1, 2).Render(header + "\n" + content + "\n" + footer)
|
||||
s := lipgloss.NewStyle().Padding(1, 2)
|
||||
if m.termHeight > 0 {
|
||||
s = s.MaxHeight(m.termHeight)
|
||||
}
|
||||
return s.Render(header + "\n" + content + "\n" + footer)
|
||||
}
|
||||
|
||||
func limitStr(text string, max int) string {
|
||||
|
||||
Reference in New Issue
Block a user