fix: six small fixes — rate limiter leak, DST SLA, probe sort, TUI cleanup #117
@@ -131,14 +131,11 @@ func ComputeDailyBreakdown(changes []models.StateChange, currentStatus models.St
|
||||
reports := make([]DayReport, days)
|
||||
|
||||
for i := 0; i < days; i++ {
|
||||
dayEnd := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Add(-time.Duration(i) * 24 * time.Hour)
|
||||
dayStart := time.Date(now.Year(), now.Month(), now.Day()-i, 0, 0, 0, 0, now.Location())
|
||||
dayEnd := time.Date(now.Year(), now.Month(), now.Day()-i+1, 0, 0, 0, 0, now.Location())
|
||||
if i == 0 {
|
||||
dayEnd = now
|
||||
}
|
||||
dayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).Add(-time.Duration(i) * 24 * time.Hour)
|
||||
if i > 0 {
|
||||
dayEnd = dayStart.Add(24 * time.Hour)
|
||||
}
|
||||
|
||||
windowChanges := filterChangesForWindow(changes, dayStart, dayEnd)
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ type RateLimiter struct {
|
||||
rate float64
|
||||
burst float64
|
||||
trusted []*net.IPNet
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
func NewRateLimiter(requestsPerMinute int, trusted []*net.IPNet) *RateLimiter {
|
||||
@@ -33,11 +34,16 @@ func NewRateLimiter(requestsPerMinute int, trusted []*net.IPNet) *RateLimiter {
|
||||
rate: float64(requestsPerMinute) / 60.0,
|
||||
burst: float64(requestsPerMinute),
|
||||
trusted: trusted,
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
go rl.cleanup()
|
||||
return rl
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) Stop() {
|
||||
close(rl.stop)
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) Allow(ip string) bool {
|
||||
rl.mu.Lock()
|
||||
defer rl.mu.Unlock()
|
||||
@@ -84,8 +90,11 @@ func (rl *RateLimiter) evictOldest() {
|
||||
}
|
||||
|
||||
func (rl *RateLimiter) cleanup() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
time.Sleep(5 * time.Minute)
|
||||
select {
|
||||
case <-ticker.C:
|
||||
rl.mu.Lock()
|
||||
cutoff := time.Now().Add(-10 * time.Minute)
|
||||
for ip, v := range rl.visitors {
|
||||
@@ -94,6 +103,9 @@ func (rl *RateLimiter) cleanup() {
|
||||
}
|
||||
}
|
||||
rl.mu.Unlock()
|
||||
case <-rl.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,10 +75,7 @@ func fmtAlertType(t string) string {
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) fmtAlertConfig(alert struct {
|
||||
Type string
|
||||
Settings map[string]string
|
||||
}) string {
|
||||
func (m Model) fmtAlertConfig(alert models.AlertConfig) string {
|
||||
switch alert.Type {
|
||||
case "email":
|
||||
host := alert.Settings["host"]
|
||||
@@ -201,10 +198,7 @@ func (m Model) viewAlertsTab() string {
|
||||
m.fmtAlertHealth(h),
|
||||
m.zones.Mark(fmt.Sprintf("alert-%d", i), limitStr(a.Name, nameW-2)),
|
||||
fmtAlertType(a.Type),
|
||||
limitStr(m.fmtAlertConfig(struct {
|
||||
Type string
|
||||
Settings map[string]string
|
||||
}{a.Type, a.Settings}), cfgW-2),
|
||||
limitStr(m.fmtAlertConfig(a), cfgW-2),
|
||||
m.fmtAlertLastSent(h),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -44,10 +44,7 @@ func TestAlertDetailPanel_MasksSecretsStableOrder(t *testing.T) {
|
||||
func TestFmtAlertConfig_MasksSecrets(t *testing.T) {
|
||||
m := newTestModel(&tuiMockStore{})
|
||||
|
||||
webhook := m.fmtAlertConfig(struct {
|
||||
Type string
|
||||
Settings map[string]string
|
||||
}{"discord", map[string]string{"url": "https://discord.com/api/webhooks/123456/SeCrEtToKeN"}})
|
||||
webhook := m.fmtAlertConfig(models.AlertConfig{Type: "discord", Settings: map[string]string{"url": "https://discord.com/api/webhooks/123456/SeCrEtToKeN"}})
|
||||
if strings.Contains(webhook, "SeCrEtToKeN") || strings.Contains(webhook, "123456") {
|
||||
t.Errorf("webhook URL path (the credential) rendered in table: %q", webhook)
|
||||
}
|
||||
@@ -55,10 +52,7 @@ func TestFmtAlertConfig_MasksSecrets(t *testing.T) {
|
||||
t.Errorf("webhook host missing from table config: %q", webhook)
|
||||
}
|
||||
|
||||
pd := m.fmtAlertConfig(struct {
|
||||
Type string
|
||||
Settings map[string]string
|
||||
}{"pagerduty", map[string]string{"routing_key": "R0123456789ABCDEFGHIJ"}})
|
||||
pd := m.fmtAlertConfig(models.AlertConfig{Type: "pagerduty", Settings: map[string]string{"routing_key": "R0123456789ABCDEFGHIJ"}})
|
||||
if strings.Contains(pd, "R0123456789ABCDEFGHIJ") {
|
||||
t.Errorf("pagerduty routing key rendered raw in table: %q", pd)
|
||||
}
|
||||
|
||||
@@ -110,10 +110,6 @@ func (m *Model) handleConfirmDelete(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
func (m *Model) handleFormMsg(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if wsm, ok := msg.(tea.WindowSizeMsg); ok {
|
||||
m.termWidth = wsm.Width
|
||||
m.termHeight = wsm.Height
|
||||
}
|
||||
if keyMsg, ok := msg.(tea.KeyMsg); ok {
|
||||
if keyMsg.String() == "ctrl+c" {
|
||||
return m, tea.Quit
|
||||
@@ -609,7 +605,7 @@ func (m *Model) handleDashboardKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
return m, writeCmd("Save theme", func() error {
|
||||
return st.SetPreference(context.Background(), "theme", name)
|
||||
})
|
||||
case "d", "backspace":
|
||||
case "d":
|
||||
return m.handleDeleteItem()
|
||||
}
|
||||
return m, nil
|
||||
|
||||
@@ -2,6 +2,7 @@ package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -163,8 +164,14 @@ func (m Model) viewDetailPanel() string {
|
||||
|
||||
probeResults := m.engine.GetProbeResults(site.ID)
|
||||
if len(probeResults) > 0 {
|
||||
nodeIDs := make([]string, 0, len(probeResults))
|
||||
for id := range probeResults {
|
||||
nodeIDs = append(nodeIDs, id)
|
||||
}
|
||||
sort.Strings(nodeIDs)
|
||||
b.WriteString("\n" + m.st.subtleStyle.Render(" PROBE RESULTS") + "\n")
|
||||
for nodeID, result := range probeResults {
|
||||
for _, nodeID := range nodeIDs {
|
||||
result := probeResults[nodeID]
|
||||
status := m.st.specialStyle.Render("UP")
|
||||
if !result.IsUp {
|
||||
status = m.st.dangerStyle.Render("DN")
|
||||
|
||||
Reference in New Issue
Block a user