diff --git a/deploy/docker-compose.cluster.yml b/deploy/docker-compose.cluster.yml index 344e08a..386d5af 100644 --- a/deploy/docker-compose.cluster.yml +++ b/deploy/docker-compose.cluster.yml @@ -18,7 +18,7 @@ services: # Cluster Config - UPTOP_CLUSTER_MODE=leader - - UPTOP_CLUSTER_SECRET=mysecret + - UPTOP_CLUSTER_SECRET=mysecret # EXAMPLE ONLY — rotate before use depends_on: - leader-db stdin_open: true @@ -53,7 +53,7 @@ services: # Cluster Config - UPTOP_CLUSTER_MODE=follower - - UPTOP_CLUSTER_SECRET=mysecret + - UPTOP_CLUSTER_SECRET=mysecret # EXAMPLE ONLY — rotate before use # IMPORTANT: Uses the Service Name "leader" to connect internally - UPTOP_PEER_URL=http://leader:8080 depends_on: diff --git a/deploy/docker-compose.probe.yml b/deploy/docker-compose.probe.yml index b741c6d..cf4b2e9 100644 --- a/deploy/docker-compose.probe.yml +++ b/deploy/docker-compose.probe.yml @@ -3,7 +3,7 @@ services: build: . environment: - UPTOP_CLUSTER_MODE=leader - - UPTOP_CLUSTER_SECRET=changeme + - UPTOP_CLUSTER_SECRET=changeme # EXAMPLE ONLY — rotate before use - UPTOP_AGG_STRATEGY=any-down - UPTOP_STATUS_ENABLED=true ports: @@ -18,7 +18,7 @@ services: - UPTOP_NODE_NAME=US East Probe - UPTOP_NODE_REGION=us-east - UPTOP_PEER_URL=http://leader:8080 - - UPTOP_CLUSTER_SECRET=changeme + - UPTOP_CLUSTER_SECRET=changeme # EXAMPLE ONLY — rotate before use depends_on: - leader @@ -30,6 +30,6 @@ services: - UPTOP_NODE_NAME=EU West Probe - UPTOP_NODE_REGION=eu-west - UPTOP_PEER_URL=http://leader:8080 - - UPTOP_CLUSTER_SECRET=changeme + - UPTOP_CLUSTER_SECRET=changeme # EXAMPLE ONLY — rotate before use depends_on: - leader diff --git a/internal/monitor/checker.go b/internal/monitor/checker.go index 1f00dd5..11eb9d5 100644 --- a/internal/monitor/checker.go +++ b/internal/monitor/checker.go @@ -36,10 +36,8 @@ type CheckResult struct { ErrorReason string } -func RunCheck(ctx context.Context, site models.SiteConfig, strict, insecure *http.Client, globalInsecure bool, allowPrivate ...bool) CheckResult { - private := len(allowPrivate) > 0 && allowPrivate[0] - - if site.Type != "http" && site.Type != "dns" && !private { +func RunCheck(ctx context.Context, site models.SiteConfig, strict, insecure *http.Client, globalInsecure, allowPrivate bool) CheckResult { + if site.Type != "http" && site.Type != "dns" && !allowPrivate { host := site.Hostname if host == "" { host = site.URL @@ -63,7 +61,7 @@ func RunCheck(ctx context.Context, site models.SiteConfig, strict, insecure *htt case "port": return runPortCheck(ctx, site) case "dns": - return runDNSCheck(ctx, site, private) + return runDNSCheck(ctx, site, allowPrivate) default: return CheckResult{SiteID: site.ID, Status: string(models.StatusDown), ErrorReason: "unsupported monitor type: " + site.Type} } diff --git a/internal/monitor/checker_test.go b/internal/monitor/checker_test.go index d0bf232..9233519 100644 --- a/internal/monitor/checker_test.go +++ b/internal/monitor/checker_test.go @@ -20,7 +20,7 @@ func TestRunCheck_HTTP_Success(t *testing.T) { defer srv.Close() site := models.SiteConfig{ID: 1, Type: "http", URL: srv.URL} - result := RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false) + result := RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false, false) if result.Status != "UP" { t.Errorf("expected UP, got %s", result.Status) @@ -40,7 +40,7 @@ func TestRunCheck_HTTP_ServerError(t *testing.T) { defer srv.Close() site := models.SiteConfig{ID: 1, Type: "http", URL: srv.URL} - result := RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false) + result := RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false, false) if result.Status != "DOWN" { t.Errorf("expected DOWN, got %s", result.Status) @@ -61,7 +61,7 @@ func TestRunCheck_HTTP_CustomAcceptedCodes(t *testing.T) { }} site := models.SiteConfig{ID: 1, Type: "http", URL: srv.URL, AcceptedCodes: "200-399"} - result := RunCheck(context.Background(), site, client, client, false) + result := RunCheck(context.Background(), site, client, client, false, false) if result.Status != "UP" { t.Errorf("expected UP with accepted 200-399, got %s", result.Status) @@ -77,7 +77,7 @@ func TestRunCheck_HTTP_MethodRespected(t *testing.T) { defer srv.Close() site := models.SiteConfig{ID: 1, Type: "http", URL: srv.URL, Method: "HEAD"} - RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false) + RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false, false) if receivedMethod != "HEAD" { t.Errorf("expected HEAD, got %s", receivedMethod) @@ -92,7 +92,7 @@ func TestRunCheck_HTTP_Timeout(t *testing.T) { defer srv.Close() site := models.SiteConfig{ID: 1, Type: "http", URL: srv.URL, Timeout: 1} - result := RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false) + result := RunCheck(context.Background(), site, http.DefaultClient, http.DefaultClient, false, false) if result.Status != "DOWN" { t.Errorf("expected DOWN on timeout, got %s", result.Status) @@ -110,7 +110,7 @@ func TestRunCheck_HTTP_SSLFields(t *testing.T) { } site := models.SiteConfig{ID: 1, Type: "http", URL: srv.URL, CheckSSL: true, IgnoreTLS: true} - result := RunCheck(context.Background(), site, http.DefaultClient, insecureClient, false) + result := RunCheck(context.Background(), site, http.DefaultClient, insecureClient, false, false) if result.Status != "UP" { t.Errorf("expected UP, got %s", result.Status) @@ -172,7 +172,7 @@ func TestRunCheck_Port_BlocksPrivateByDefault(t *testing.T) { port, _ := strconv.Atoi(portStr) site := models.SiteConfig{ID: 1, Type: "port", Hostname: "127.0.0.1", Port: port, Timeout: 2} - result := RunCheck(context.Background(), site, nil, nil, false) + result := RunCheck(context.Background(), site, nil, nil, false, false) if result.Status != "DOWN" { t.Errorf("expected DOWN when private targets blocked, got %s", result.Status) @@ -181,7 +181,7 @@ func TestRunCheck_Port_BlocksPrivateByDefault(t *testing.T) { func TestRunCheck_UnknownType(t *testing.T) { site := models.SiteConfig{ID: 1, Type: "invalid"} - result := RunCheck(context.Background(), site, nil, nil, false) + result := RunCheck(context.Background(), site, nil, nil, false, false) if result.Status != "DOWN" { t.Errorf("expected DOWN for unknown type, got %s", result.Status) diff --git a/internal/store/sqlstore.go b/internal/store/sqlstore.go index 285bec8..e322710 100644 --- a/internal/store/sqlstore.go +++ b/internal/store/sqlstore.go @@ -17,7 +17,6 @@ const ( maxLogRows = 200 maxStateChangesPerSite = 5000 maxMaintenanceExport = 1000 - maxRequestBody = 1 << 20 ) type SQLStore struct { @@ -154,7 +153,9 @@ func (s *SQLStore) AddSite(ctx context.Context, site models.SiteConfig) error { func (s *SQLStore) UpdateSite(ctx context.Context, site models.SiteConfig) error { var existingToken string - _ = s.db.QueryRowContext(ctx, s.q("SELECT token FROM sites WHERE id=?"), site.ID).Scan(&existingToken) //nolint:errcheck + if err := s.db.QueryRowContext(ctx, s.q("SELECT token FROM sites WHERE id=?"), site.ID).Scan(&existingToken); err != nil && err != sql.ErrNoRows { + return fmt.Errorf("read existing token: %w", err) + } if site.Type == "push" && existingToken == "" { var err error existingToken, err = generateToken() diff --git a/internal/tui/update.go b/internal/tui/update.go index 633cc2b..194d396 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -141,23 +141,34 @@ func (m *Model) handleFormMsg(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } -func (m *Model) handleResize(msg tea.WindowSizeMsg) (tea.Model, tea.Cmd) { - m.termWidth = msg.Width - m.termHeight = msg.Height +func (m *Model) recalcLayout() { chrome := chromeBase if m.filterMode || m.filterText != "" { chrome++ } - m.maxTableRows = msg.Height - chrome + m.maxTableRows = m.termHeight - chrome if m.maxTableRows < 1 { m.maxTableRows = 1 } +} + +func (m *Model) handleResize(msg tea.WindowSizeMsg) (tea.Model, tea.Cmd) { + m.termWidth = msg.Width + m.termHeight = msg.Height + m.recalcLayout() m.logViewport.Width = msg.Width - chromePadH m.logViewport.Height = msg.Height - (chromePadV + chromeHeader + chromeFooter + 2) m.historyViewport.Width = msg.Width - chromePadH m.historyViewport.Height = msg.Height - 10 m.slaViewport.Width = msg.Width - chromePadH m.slaViewport.Height = msg.Height - 16 + if m.huhForm != nil { + formHeight := msg.Height - 7 + if formHeight < 5 { + formHeight = 5 + } + m.huhForm.WithHeight(formHeight) + } return m, nil } @@ -320,9 +331,11 @@ func (m *Model) handleFilterKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { m.filterText = "" m.cursor = 0 m.tableOffset = 0 + m.recalcLayout() m.refreshLive() case "enter": m.filterMode = false + m.recalcLayout() case "backspace": if len(m.filterText) > 0 { m.filterText = m.filterText[:len(m.filterText)-1] @@ -508,6 +521,7 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { case "/": if m.currentTab == 0 { m.filterMode = true + m.recalcLayout() return m, nil } case "f": @@ -740,16 +754,8 @@ func (m *Model) switchTab(idx int) { } } -func (m *Model) adjustCursor(newLen int) { - if m.cursor >= newLen && m.cursor > 0 { - m.cursor-- - } - if m.cursor < m.tableOffset { - m.tableOffset = m.cursor - if m.tableOffset < 0 { - m.tableOffset = 0 - } - } +func (m *Model) adjustCursor(_ int) { + m.clampCursor() } func (m *Model) submitForm() tea.Cmd { diff --git a/internal/tui/view_dashboard.go b/internal/tui/view_dashboard.go index 4335dad..5126cf4 100644 --- a/internal/tui/view_dashboard.go +++ b/internal/tui/view_dashboard.go @@ -85,11 +85,6 @@ func (m Model) View() string { case stateFormMaint: title = "New Maintenance Window" } - formHeight := m.termHeight - 7 - if formHeight < 5 { - formHeight = 5 - } - m.huhForm.WithHeight(formHeight) header := m.st.titleStyle.Render(title) footer := m.st.subtleStyle.Render("\n[Esc] Cancel") return lipgloss.NewStyle().Padding(1, 2).Render(header + "\n\n" + m.huhForm.View() + "\n" + footer) @@ -270,7 +265,7 @@ func (m Model) renderFooter(stats dashboardStats) string { var keys string switch m.currentTab { case 0: - keys = "[/]Filter [n]New [e]Edit [i]Info [d]Del [p]Pause [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: keys = "[n]New [e]Edit [i]Info [d]Del [t]Test [T]Theme [Tab]Switch [q]Quit" case 2: