Skip to content

Commit 1aad7ec

Browse files
Merge pull request #65 from hhftechnology/pangolin
Pangolin
2 parents 8c92c4f + ba764af commit 1aad7ec

4 files changed

Lines changed: 54 additions & 16 deletions

File tree

internal/api/handlers/health_diagnostics.go

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,30 +31,43 @@ func parseBouncersJSON(bouncerOutput string, computeStatus bool) ([]models.Bounc
3131
if ipAddr, err := jsonparser.GetString(value, "ip_address"); err == nil {
3232
bouncer.IPAddress = ipAddr
3333
}
34-
if valid, err := jsonparser.GetBoolean(value, "valid"); err == nil {
35-
bouncer.Valid = valid
36-
}
37-
if lastPull, err := jsonparser.GetString(value, "last_pull"); err == nil {
38-
for _, layout := range []string{time.RFC3339Nano, time.RFC3339} {
39-
if t, err := time.Parse(layout, lastPull); err == nil {
40-
bouncer.LastPull = t
41-
break
34+
// Parse timestamps with multiple format support
35+
parseTime := func(key string) time.Time {
36+
if s, err := jsonparser.GetString(value, key); err == nil {
37+
for _, layout := range []string{time.RFC3339Nano, time.RFC3339} {
38+
if t, err := time.Parse(layout, s); err == nil {
39+
return t
40+
}
4241
}
4342
}
43+
return time.Time{}
4444
}
45+
46+
bouncer.LastPull = parseTime("last_pull")
47+
bouncer.CreatedAt = parseTime("created_at")
48+
bouncer.UpdatedAt = parseTime("updated_at")
49+
4550
if bouncerType, err := jsonparser.GetString(value, "type"); err == nil {
4651
bouncer.Type = bouncerType
4752
}
4853
if version, err := jsonparser.GetString(value, "version"); err == nil {
4954
bouncer.Version = version
5055
}
5156

57+
// A bouncer that exists in the list is valid by definition.
58+
// CrowdSec deletes revoked bouncers rather than marking them,
59+
// so the "revoked" and legacy "valid" fields are not reliable
60+
// indicators of actual connectivity (confirmed by CrowdSec team, see #47).
61+
bouncer.Valid = true
62+
5263
if computeStatus {
53-
if !bouncer.Valid {
54-
bouncer.Status = "disconnected"
55-
} else if bouncer.LastPull.IsZero() {
56-
bouncer.Status = "pending" // valid key, never pulled yet
57-
} else if time.Since(bouncer.LastPull) <= 60*time.Minute {
64+
lastActivity := bouncer.LastActivity()
65+
66+
if !lastActivity.IsZero() && time.Since(lastActivity) <= 5*time.Minute {
67+
bouncer.Status = "connected"
68+
} else if lastActivity.IsZero() {
69+
bouncer.Status = "registered" // enrolled but never pulled
70+
} else if time.Since(lastActivity) <= 60*time.Minute {
5871
bouncer.Status = "connected"
5972
} else {
6073
bouncer.Status = "stale"
@@ -99,7 +112,8 @@ func checkBouncersHealth(dockerClient *docker.Client, containerName string) mode
99112

100113
activeBouncers := 0
101114
for _, b := range bouncers {
102-
if b.Valid && time.Since(b.LastPull) <= 60*time.Minute {
115+
lastActivity := b.LastActivity()
116+
if !lastActivity.IsZero() && time.Since(lastActivity) <= 60*time.Minute {
103117
activeBouncers++
104118
}
105119
}

internal/models/models.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,27 @@ type Bouncer struct {
151151
IPAddress string `json:"ip_address"`
152152
Valid bool `json:"valid"`
153153
LastPull time.Time `json:"last_pull"`
154+
CreatedAt time.Time `json:"created_at"`
155+
UpdatedAt time.Time `json:"updated_at"`
154156
Type string `json:"type"`
155157
Version string `json:"version"`
156158
Status string `json:"status"`
157159
}
158160

161+
// LastActivity returns the best-known last activity time for this bouncer.
162+
// It prefers LastPull; falls back to UpdatedAt only when LastPull is zero
163+
// and UpdatedAt meaningfully exceeds CreatedAt (both must be non-zero).
164+
func (b Bouncer) LastActivity() time.Time {
165+
if !b.LastPull.IsZero() {
166+
return b.LastPull
167+
}
168+
if !b.CreatedAt.IsZero() && !b.UpdatedAt.IsZero() &&
169+
b.UpdatedAt.After(b.CreatedAt.Add(5*time.Second)) {
170+
return b.UpdatedAt
171+
}
172+
return time.Time{}
173+
}
174+
159175
// IPInfo represents IP address information
160176
type IPInfo struct {
161177
IP string `json:"ip"`

web/src/components/health/BouncerStatusMonitor.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,14 @@ function BouncerStatusMonitor({ bouncers, className }: BouncerStatusMonitorProps
5151
<TableCell>{bouncer.type || '-'}</TableCell>
5252
<TableCell>{bouncer.version || '-'}</TableCell>
5353
<TableCell>
54-
<Badge variant={bouncer.valid ? 'success' : 'destructive'}>
55-
{bouncer.valid ? 'Valid' : 'Invalid'}
54+
<Badge variant={
55+
bouncer.status === 'connected' ? 'success' :
56+
bouncer.status === 'stale' ? 'warning' :
57+
bouncer.status === 'registered' ? 'outline' :
58+
bouncer.status === 'disconnected' ? 'destructive' :
59+
bouncer.valid ? 'success' : 'destructive'
60+
}>
61+
{bouncer.status || (bouncer.valid ? 'Valid' : 'Invalid')}
5662
</Badge>
5763
</TableCell>
5864
<TableCell className="text-sm text-muted-foreground">

web/src/pages/Bouncers.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ export default function Bouncers() {
124124
return <Badge variant="warning">Stale</Badge>
125125
case 'pending':
126126
return <Badge variant="warning">Pending</Badge>
127+
case 'registered':
128+
return <Badge variant="outline">Registered</Badge>
127129
default:
128130
return <Badge variant="secondary">{status || 'Unknown'}</Badge>
129131
}

0 commit comments

Comments
 (0)