From d5ab3a18a4a93d4bdf7a7da16186c2953c12a904 Mon Sep 17 00:00:00 2001 From: Tyler Koenig Date: Thu, 14 May 2026 18:46:17 -0400 Subject: [PATCH] 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. --- internal/models/models.go | 1 + internal/monitor/monitor.go | 26 ++++++++- internal/server/server.go | 105 +++++++++++++++++++++++++++++++----- internal/store/postgres.go | 23 ++++---- internal/store/sqlite.go | 23 ++++---- internal/store/store.go | 1 + internal/tui/tab_alerts.go | 22 ++++---- internal/tui/tab_sites.go | 29 +++++----- internal/tui/tab_users.go | 22 ++++---- internal/tui/tui.go | 24 +++++++-- 10 files changed, 199 insertions(+), 77 deletions(-) diff --git a/internal/models/models.go b/internal/models/models.go index f79c9c5..467fd33 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -24,6 +24,7 @@ type Site struct { DNSResolveType string DNSServer string IgnoreTLS bool + Paused bool FailureCount int Status string diff --git a/internal/monitor/monitor.go b/internal/monitor/monitor.go index 791aac1..76a55bb 100644 --- a/internal/monitor/monitor.go +++ b/internal/monitor/monitor.go @@ -162,6 +162,7 @@ func UpdateSiteConfig(site models.Site) { s.DNSResolveType = site.DNSResolveType s.DNSServer = site.DNSServer s.IgnoreTLS = site.IgnoreTLS + s.Paused = site.Paused LiveState[site.ID] = s } } @@ -173,10 +174,26 @@ func RemoveSite(id int) { RemoveHistory(id) } +func ToggleSitePause(id int) bool { + Mutex.Lock() + defer Mutex.Unlock() + site, ok := LiveState[id] + if !ok { + return false + } + site.Paused = !site.Paused + LiveState[id] = site + if site.Paused { + AddLog(fmt.Sprintf("Monitor '%s' paused", site.Name)) + } else { + AddLog(fmt.Sprintf("Monitor '%s' resumed", site.Name)) + } + return site.Paused +} + func monitorRoutine(id int) { checkByID(id) for { - // If paused, just sleep loop to keep goroutine alive but idle if !IsEngineActive() { time.Sleep(5 * time.Second) continue @@ -189,6 +206,11 @@ func monitorRoutine(id int) { return } + if site.Paused { + time.Sleep(5 * time.Second) + continue + } + interval := site.Interval if interval < 5 { interval = 5 @@ -206,7 +228,7 @@ func checkByID(id int) { Mutex.RLock() site, exists := LiveState[id] Mutex.RUnlock() - if !exists { + if !exists || site.Paused { return } switch site.Type { diff --git a/internal/server/server.go b/internal/server/server.go index 6e857b4..89460ed 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -148,7 +148,6 @@ func renderStatusPage(w http.ResponseWriter, title string) { {{.Title}} -

{{.Title}}

- {{range .Sites}} -
-
-
{{.Name}}
-
{{.Type}} | {{if eq .Type "http"}}{{.URL}}{{else}}Heartbeat Monitor{{end}}
-
Last Check: {{.LastCheck.Format "15:04:05"}}
-
-
{{.Status}}
-
- {{end}} +
+
+
Powered by Go-Upkeep
` diff --git a/internal/store/postgres.go b/internal/store/postgres.go index 34ce96b..ea7f58b 100644 --- a/internal/store/postgres.go +++ b/internal/store/postgres.go @@ -47,7 +47,8 @@ func (p *PostgresStore) Init() error { accepted_codes TEXT DEFAULT '200-299', dns_resolve_type TEXT DEFAULT '', dns_server TEXT DEFAULT '', - ignore_tls BOOLEAN DEFAULT FALSE + ignore_tls BOOLEAN DEFAULT FALSE, + paused BOOLEAN DEFAULT FALSE );`, `CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, @@ -73,6 +74,7 @@ func (p *PostgresStore) Init() error { "ALTER TABLE sites ADD COLUMN IF NOT EXISTS dns_resolve_type TEXT DEFAULT ''", "ALTER TABLE sites ADD COLUMN IF NOT EXISTS dns_server TEXT DEFAULT ''", "ALTER TABLE sites ADD COLUMN IF NOT EXISTS ignore_tls BOOLEAN DEFAULT FALSE", + "ALTER TABLE sites ADD COLUMN IF NOT EXISTS paused BOOLEAN DEFAULT FALSE", } for _, m := range migrations { p.db.Exec(m) @@ -83,7 +85,7 @@ func (p *PostgresStore) Init() error { // ... [CRUD Methods are identical to Phase 4, keeping them concise here] ... func (p *PostgresStore) GetSites() []models.Site { - rows, err := p.db.Query("SELECT id, COALESCE(name, url), url, COALESCE(type, 'http'), COALESCE(token, ''), interval, alert_id, check_ssl, threshold, max_retries, COALESCE(hostname, ''), COALESCE(port, 0), COALESCE(timeout, 0), COALESCE(method, 'GET'), COALESCE(description, ''), COALESCE(parent_id, 0), COALESCE(accepted_codes, '200-299'), COALESCE(dns_resolve_type, ''), COALESCE(dns_server, ''), COALESCE(ignore_tls, FALSE) FROM sites") + rows, err := p.db.Query("SELECT id, COALESCE(name, url), url, COALESCE(type, 'http'), COALESCE(token, ''), interval, alert_id, check_ssl, threshold, max_retries, COALESCE(hostname, ''), COALESCE(port, 0), COALESCE(timeout, 0), COALESCE(method, 'GET'), COALESCE(description, ''), COALESCE(parent_id, 0), COALESCE(accepted_codes, '200-299'), COALESCE(dns_resolve_type, ''), COALESCE(dns_server, ''), COALESCE(ignore_tls, FALSE), COALESCE(paused, FALSE) FROM sites") if err != nil { return []models.Site{} } @@ -92,7 +94,7 @@ func (p *PostgresStore) GetSites() []models.Site { for rows.Next() { var s models.Site rows.Scan(&s.ID, &s.Name, &s.URL, &s.Type, &s.Token, &s.Interval, &s.AlertID, &s.CheckSSL, &s.ExpiryThreshold, &s.MaxRetries, - &s.Hostname, &s.Port, &s.Timeout, &s.Method, &s.Description, &s.ParentID, &s.AcceptedCodes, &s.DNSResolveType, &s.DNSServer, &s.IgnoreTLS) + &s.Hostname, &s.Port, &s.Timeout, &s.Method, &s.Description, &s.ParentID, &s.AcceptedCodes, &s.DNSResolveType, &s.DNSServer, &s.IgnoreTLS, &s.Paused) sites = append(sites, s) } return sites @@ -102,9 +104,9 @@ func (p *PostgresStore) AddSite(site models.Site) { if site.Type == "push" { token = generateToken() } - p.db.Exec("INSERT INTO sites (name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)", + p.db.Exec("INSERT INTO sites (name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls, paused) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)", site.Name, site.URL, site.Type, token, site.Interval, site.AlertID, site.CheckSSL, site.ExpiryThreshold, site.MaxRetries, - site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS) + site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS, site.Paused) } func (p *PostgresStore) UpdateSite(site models.Site) { var existingToken string @@ -112,9 +114,12 @@ func (p *PostgresStore) UpdateSite(site models.Site) { if site.Type == "push" && existingToken == "" { existingToken = generateToken() } - p.db.Exec("UPDATE sites SET name=$1, url=$2, type=$3, token=$4, interval=$5, alert_id=$6, check_ssl=$7, threshold=$8, max_retries=$9, hostname=$10, port=$11, timeout=$12, method=$13, description=$14, parent_id=$15, accepted_codes=$16, dns_resolve_type=$17, dns_server=$18, ignore_tls=$19 WHERE id=$20", + p.db.Exec("UPDATE sites SET name=$1, url=$2, type=$3, token=$4, interval=$5, alert_id=$6, check_ssl=$7, threshold=$8, max_retries=$9, hostname=$10, port=$11, timeout=$12, method=$13, description=$14, parent_id=$15, accepted_codes=$16, dns_resolve_type=$17, dns_server=$18, ignore_tls=$19, paused=$20 WHERE id=$21", site.Name, site.URL, site.Type, existingToken, site.Interval, site.AlertID, site.CheckSSL, site.ExpiryThreshold, site.MaxRetries, - site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS, site.ID) + site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS, site.Paused, site.ID) +} +func (p *PostgresStore) UpdateSitePaused(id int, paused bool) { + p.db.Exec("UPDATE sites SET paused=$1 WHERE id=$2", paused, id) } func (p *PostgresStore) DeleteSite(id int) { p.db.Exec("DELETE FROM sites WHERE id=$1", id) } func (p *PostgresStore) GetAllAlerts() []models.AlertConfig { @@ -207,9 +212,9 @@ func (p *PostgresStore) ImportData(data models.Backup) error { tx.Exec("INSERT INTO alerts (id, name, type, settings) VALUES ($1, $2, $3, $4)", a.ID, a.Name, a.Type, string(jsonBytes)) } for _, st := range data.Sites { - tx.Exec("INSERT INTO sites (id, name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)", + tx.Exec("INSERT INTO sites (id, name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls, paused) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)", st.ID, st.Name, st.URL, st.Type, st.Token, st.Interval, st.AlertID, st.CheckSSL, st.ExpiryThreshold, st.MaxRetries, - st.Hostname, st.Port, st.Timeout, st.Method, st.Description, st.ParentID, st.AcceptedCodes, st.DNSResolveType, st.DNSServer, st.IgnoreTLS) + st.Hostname, st.Port, st.Timeout, st.Method, st.Description, st.ParentID, st.AcceptedCodes, st.DNSResolveType, st.DNSServer, st.IgnoreTLS, st.Paused) } tx.Exec("SELECT setval('sites_id_seq', (SELECT MAX(id) FROM sites))") diff --git a/internal/store/sqlite.go b/internal/store/sqlite.go index d55a35e..a0331cd 100644 --- a/internal/store/sqlite.go +++ b/internal/store/sqlite.go @@ -49,7 +49,8 @@ func (s *SQLiteStore) Init() error { accepted_codes TEXT DEFAULT '200-299', dns_resolve_type TEXT DEFAULT '', dns_server TEXT DEFAULT '', - ignore_tls BOOLEAN DEFAULT 0 + ignore_tls BOOLEAN DEFAULT 0, + paused BOOLEAN DEFAULT 0 ); CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -73,6 +74,7 @@ func (s *SQLiteStore) Init() error { "ALTER TABLE sites ADD COLUMN dns_resolve_type TEXT DEFAULT ''", "ALTER TABLE sites ADD COLUMN dns_server TEXT DEFAULT ''", "ALTER TABLE sites ADD COLUMN ignore_tls BOOLEAN DEFAULT 0", + "ALTER TABLE sites ADD COLUMN paused BOOLEAN DEFAULT 0", } for _, m := range migrations { s.db.Exec(m) @@ -90,7 +92,7 @@ func generateToken() string { } func (s *SQLiteStore) GetSites() []models.Site { - rows, err := s.db.Query("SELECT id, COALESCE(name, url), url, COALESCE(type, 'http'), COALESCE(token, ''), interval, alert_id, check_ssl, threshold, max_retries, COALESCE(hostname, ''), COALESCE(port, 0), COALESCE(timeout, 0), COALESCE(method, 'GET'), COALESCE(description, ''), COALESCE(parent_id, 0), COALESCE(accepted_codes, '200-299'), COALESCE(dns_resolve_type, ''), COALESCE(dns_server, ''), COALESCE(ignore_tls, 0) FROM sites") + rows, err := s.db.Query("SELECT id, COALESCE(name, url), url, COALESCE(type, 'http'), COALESCE(token, ''), interval, alert_id, check_ssl, threshold, max_retries, COALESCE(hostname, ''), COALESCE(port, 0), COALESCE(timeout, 0), COALESCE(method, 'GET'), COALESCE(description, ''), COALESCE(parent_id, 0), COALESCE(accepted_codes, '200-299'), COALESCE(dns_resolve_type, ''), COALESCE(dns_server, ''), COALESCE(ignore_tls, 0), COALESCE(paused, 0) FROM sites") if err != nil { return []models.Site{} } @@ -98,7 +100,7 @@ func (s *SQLiteStore) GetSites() []models.Site { var sites []models.Site for rows.Next() { var st models.Site - rows.Scan(&st.ID, &st.Name, &st.URL, &st.Type, &st.Token, &st.Interval, &st.AlertID, &st.CheckSSL, &st.ExpiryThreshold, &st.MaxRetries, &st.Hostname, &st.Port, &st.Timeout, &st.Method, &st.Description, &st.ParentID, &st.AcceptedCodes, &st.DNSResolveType, &st.DNSServer, &st.IgnoreTLS) + rows.Scan(&st.ID, &st.Name, &st.URL, &st.Type, &st.Token, &st.Interval, &st.AlertID, &st.CheckSSL, &st.ExpiryThreshold, &st.MaxRetries, &st.Hostname, &st.Port, &st.Timeout, &st.Method, &st.Description, &st.ParentID, &st.AcceptedCodes, &st.DNSResolveType, &st.DNSServer, &st.IgnoreTLS, &st.Paused) sites = append(sites, st) } return sites @@ -108,9 +110,9 @@ func (s *SQLiteStore) AddSite(site models.Site) { if site.Type == "push" { token = generateToken() } - s.db.Exec("INSERT INTO sites (name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + s.db.Exec("INSERT INTO sites (name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls, paused) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", site.Name, site.URL, site.Type, token, site.Interval, site.AlertID, site.CheckSSL, site.ExpiryThreshold, site.MaxRetries, - site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS) + site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS, site.Paused) } func (s *SQLiteStore) UpdateSite(site models.Site) { var existingToken string @@ -118,9 +120,12 @@ func (s *SQLiteStore) UpdateSite(site models.Site) { if site.Type == "push" && existingToken == "" { existingToken = generateToken() } - s.db.Exec("UPDATE sites SET name=?, url=?, type=?, token=?, interval=?, alert_id=?, check_ssl=?, threshold=?, max_retries=?, hostname=?, port=?, timeout=?, method=?, description=?, parent_id=?, accepted_codes=?, dns_resolve_type=?, dns_server=?, ignore_tls=? WHERE id=?", + s.db.Exec("UPDATE sites SET name=?, url=?, type=?, token=?, interval=?, alert_id=?, check_ssl=?, threshold=?, max_retries=?, hostname=?, port=?, timeout=?, method=?, description=?, parent_id=?, accepted_codes=?, dns_resolve_type=?, dns_server=?, ignore_tls=?, paused=? WHERE id=?", site.Name, site.URL, site.Type, existingToken, site.Interval, site.AlertID, site.CheckSSL, site.ExpiryThreshold, site.MaxRetries, - site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS, site.ID) + site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS, site.Paused, site.ID) +} +func (s *SQLiteStore) UpdateSitePaused(id int, paused bool) { + s.db.Exec("UPDATE sites SET paused=? WHERE id=?", paused, id) } func (s *SQLiteStore) DeleteSite(id int) { s.db.Exec("DELETE FROM sites WHERE id=?", id) @@ -232,9 +237,9 @@ func (s *SQLiteStore) ImportData(data models.Backup) error { tx.Exec("INSERT INTO alerts (id, name, type, settings) VALUES (?, ?, ?, ?)", a.ID, a.Name, a.Type, string(jsonBytes)) } for _, st := range data.Sites { - tx.Exec("INSERT INTO sites (id, name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + tx.Exec("INSERT INTO sites (id, name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls, paused) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", st.ID, st.Name, st.URL, st.Type, st.Token, st.Interval, st.AlertID, st.CheckSSL, st.ExpiryThreshold, st.MaxRetries, - st.Hostname, st.Port, st.Timeout, st.Method, st.Description, st.ParentID, st.AcceptedCodes, st.DNSResolveType, st.DNSServer, st.IgnoreTLS) + st.Hostname, st.Port, st.Timeout, st.Method, st.Description, st.ParentID, st.AcceptedCodes, st.DNSResolveType, st.DNSServer, st.IgnoreTLS, st.Paused) } return tx.Commit() diff --git a/internal/store/store.go b/internal/store/store.go index e9c05ac..85b47f6 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -11,6 +11,7 @@ type Store interface { GetSites() []models.Site AddSite(site models.Site) UpdateSite(site models.Site) + UpdateSitePaused(id int, paused bool) DeleteSite(id int) // Alerts diff --git a/internal/tui/tab_alerts.go b/internal/tui/tab_alerts.go index c0ee2ec..4aa9c54 100644 --- a/internal/tui/tab_alerts.go +++ b/internal/tui/tab_alerts.go @@ -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() diff --git a/internal/tui/tab_sites.go b/internal/tui/tab_sites.go index 2ac265b..8d7a6dc 100644 --- a/internal/tui/tab_sites.go +++ b/internal/tui/tab_sites.go @@ -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() diff --git a/internal/tui/tab_users.go b/internal/tui/tab_users.go index 48fd9a3..14f9a93 100644 --- a/internal/tui/tab_users.go +++ b/internal/tui/tab_users.go @@ -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() diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 7af994b..6f9c5ed 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -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 {