Skip to content

Commit 1fcd5ab

Browse files
committed
decision-metrics-bugfix
1 parent 98e5177 commit 1fcd5ab

4 files changed

Lines changed: 66 additions & 107 deletions

File tree

internal/api/handlers/common.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,62 @@ func parseDecisionNode(data []byte) models.Decision {
254254
return d
255255
}
256256

257+
// ParseDecisionsFromOutput parses the raw JSON output of cscli decisions list
258+
// and extracts the nested decisions effectively.
259+
func ParseDecisionsFromOutput(output string) ([]models.Decision, error) {
260+
// Parse alerts using normalized CLI JSON output.
261+
var decisions []models.Decision
262+
dataBytes, parseErr := parseCLIJSONToBytes(output)
263+
if parseErr != nil {
264+
return nil, fmt.Errorf("failed to normalize decisions JSON: %w", parseErr)
265+
}
266+
267+
_, err := jsonparser.ArrayEach(dataBytes, func(alertValue []byte, alertType jsonparser.ValueType, alertOffset int, alertErr error) {
268+
// Get alert's created_at for fallback
269+
var alertCreatedAt string
270+
if createdAt, err := jsonparser.GetString(alertValue, "created_at"); err == nil {
271+
alertCreatedAt = createdAt
272+
}
273+
274+
// Get alert's ID
275+
var alertID int64
276+
if id, err := jsonparser.GetInt(alertValue, "id"); err == nil {
277+
alertID = id
278+
}
279+
280+
// Parse decisions array within this alert
281+
foundNested := false
282+
jsonparser.ArrayEach(alertValue, func(decisionValue []byte, decisionType jsonparser.ValueType, decisionOffset int, decisionErr error) {
283+
foundNested = true
284+
decision := parseDecisionNode(decisionValue)
285+
if decision.CreatedAt == "" {
286+
decision.CreatedAt = alertCreatedAt
287+
}
288+
decision.AlertID = alertID
289+
decisions = append(decisions, decision)
290+
}, "decisions")
291+
292+
// Fallback: if no nested decisions found, check if the top-level
293+
// item itself has decision fields (manual decisions via cscli decisions add)
294+
if !foundNested {
295+
if _, _, _, err := jsonparser.Get(alertValue, "type"); err == nil {
296+
decision := parseDecisionNode(alertValue)
297+
if decision.CreatedAt == "" {
298+
decision.CreatedAt = alertCreatedAt
299+
}
300+
decision.AlertID = alertID
301+
decisions = append(decisions, decision)
302+
}
303+
}
304+
})
305+
306+
if err != nil {
307+
return nil, fmt.Errorf("failed to parse alerts JSON: %w", err)
308+
}
309+
310+
return decisions, nil
311+
}
312+
257313
// CLIFlag represents a CLI flag and its value for building cscli commands.
258314
type CLIFlag struct {
259315
Flag string

internal/api/handlers/dashboard.go

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"crowdsec-manager/internal/logger"
1717
"crowdsec-manager/internal/models"
1818

19-
"github.com/buger/jsonparser"
2019
"github.com/gin-gonic/gin"
2120
)
2221

@@ -68,65 +67,16 @@ func GetDecisions(dockerClient *docker.Client, cfg *config.Config, ttlCache ...*
6867
}
6968

7069
// Parse alerts using normalized CLI JSON output.
71-
var decisions []models.Decision
72-
dataBytes, parseErr := parseCLIJSONToBytes(output)
70+
decisions, parseErr := ParseDecisionsFromOutput(output)
7371
if parseErr != nil {
74-
logger.Error("Failed to normalize decisions JSON", "error", parseErr, "output_preview", truncateString(output, 200))
72+
logger.Error("Failed to parse decisions JSON", "error", parseErr, "output_preview", truncateString(output, 200))
7573
c.JSON(http.StatusInternalServerError, models.Response{
7674
Success: false,
7775
Error: fmt.Sprintf("Failed to parse decisions JSON: %v", parseErr),
7876
})
7977
return
8078
}
8179

82-
_, err = jsonparser.ArrayEach(dataBytes, func(alertValue []byte, alertType jsonparser.ValueType, alertOffset int, alertErr error) {
83-
// Get alert's created_at for fallback
84-
var alertCreatedAt string
85-
if createdAt, err := jsonparser.GetString(alertValue, "created_at"); err == nil {
86-
alertCreatedAt = createdAt
87-
}
88-
89-
// Get alert's ID
90-
var alertID int64
91-
if id, err := jsonparser.GetInt(alertValue, "id"); err == nil {
92-
alertID = id
93-
}
94-
95-
// Parse decisions array within this alert
96-
foundNested := false
97-
jsonparser.ArrayEach(alertValue, func(decisionValue []byte, decisionType jsonparser.ValueType, decisionOffset int, decisionErr error) {
98-
foundNested = true
99-
decision := parseDecisionNode(decisionValue)
100-
if decision.CreatedAt == "" {
101-
decision.CreatedAt = alertCreatedAt
102-
}
103-
decision.AlertID = alertID
104-
decisions = append(decisions, decision)
105-
}, "decisions")
106-
107-
// Fallback: if no nested decisions found, check if the top-level
108-
// item itself has decision fields (manual decisions via cscli decisions add)
109-
if !foundNested {
110-
if _, _, _, err := jsonparser.Get(alertValue, "type"); err == nil {
111-
decision := parseDecisionNode(alertValue)
112-
if decision.CreatedAt == "" {
113-
decision.CreatedAt = alertCreatedAt
114-
}
115-
decision.AlertID = alertID
116-
decisions = append(decisions, decision)
117-
}
118-
}
119-
})
120-
121-
if err != nil {
122-
logger.Error("Failed to parse alerts JSON", "error", err, "output_preview", truncateString(output, 200))
123-
c.JSON(http.StatusInternalServerError, models.Response{
124-
Success: false,
125-
Error: fmt.Sprintf("Failed to parse decisions JSON: %v", err),
126-
})
127-
return
128-
}
129-
13080
logger.Debug("Decisions retrieved successfully", "count", len(decisions))
13181

13282
// Summary mode: return only count and lightweight aggregations

internal/api/handlers/health.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,14 @@ func RunCompleteDiagnostics(dockerClient *docker.Client, db *database.Database,
180180
var decisions []models.Decision
181181
decisionOutput, err := dockerClient.ExecCommand(cfg.CrowdsecContainerName, []string{"cscli", "decisions", "list", "-o", "json"})
182182
if err == nil {
183-
decisions = parseDiagnosticDecisions(decisionOutput)
184-
if len(decisions) > 0 {
185-
logger.Debug("Decisions retrieved successfully", "count", len(decisions))
183+
parsed, parseErr := ParseDecisionsFromOutput(decisionOutput)
184+
if parseErr != nil {
185+
logger.Warn("Failed to parse decisions JSON", "error", parseErr)
186+
} else {
187+
decisions = parsed
188+
if len(decisions) > 0 {
189+
logger.Debug("Decisions retrieved successfully", "count", len(decisions))
190+
}
186191
}
187192
} else {
188193
logger.Warn("Failed to execute decisions command", "error", err)

internal/api/handlers/health_diagnostics.go

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"crowdsec-manager/internal/config"
1010
"crowdsec-manager/internal/database"
1111
"crowdsec-manager/internal/docker"
12-
"crowdsec-manager/internal/logger"
1312
"crowdsec-manager/internal/models"
1413

1514
"github.com/buger/jsonparser"
@@ -173,57 +172,6 @@ func collectContainerHealth(dockerClient *docker.Client, cfg *config.Config) ([]
173172
return containers, allRunning
174173
}
175174

176-
// parseDiagnosticDecisions parses decisions from cscli output for diagnostics
177-
func parseDiagnosticDecisions(decisionOutput string) []models.Decision {
178-
var decisions []models.Decision
179-
180-
var rawDecisions []map[string]interface{}
181-
dataBytes, parseErr := parseCLIJSONToBytes(decisionOutput)
182-
if parseErr != nil {
183-
logger.Warn("Failed to normalize decisions JSON",
184-
"error", parseErr,
185-
"output_length", len(decisionOutput),
186-
"output_preview", truncateString(decisionOutput, 100))
187-
return decisions
188-
}
189-
if err := json.Unmarshal(dataBytes, &rawDecisions); err != nil {
190-
logger.Warn("Failed to parse decisions JSON",
191-
"error", err,
192-
"output_length", len(decisionOutput),
193-
"output_preview", truncateString(decisionOutput, 100))
194-
return decisions
195-
}
196-
197-
decisions = make([]models.Decision, 0, len(rawDecisions))
198-
for _, raw := range rawDecisions {
199-
decision := models.Decision{
200-
ID: int64(getInt(raw, "id")),
201-
Duration: getString(raw, "duration"),
202-
}
203-
204-
decision.Source = getString(raw, "source")
205-
if decision.Source == "" {
206-
decision.Source = getString(raw, "origin")
207-
}
208-
decision.Origin = decision.Source
209-
210-
decision.Type = getString(raw, "type")
211-
decision.Scope = getString(raw, "scope")
212-
decision.Value = getString(raw, "value")
213-
214-
decision.Scenario = getString(raw, "scenario")
215-
if decision.Scenario == "" {
216-
decision.Scenario = getString(raw, "reason")
217-
}
218-
decision.Reason = decision.Scenario
219-
220-
decision.CreatedAt = getString(raw, "created_at")
221-
222-
decisions = append(decisions, decision)
223-
}
224-
225-
return decisions
226-
}
227175

228176
// checkTraefikIntegrationDiagnostic checks Traefik integration for diagnostics
229177
func checkTraefikIntegrationDiagnostic(dockerClient *docker.Client, db *database.Database, cfg *config.Config) *models.TraefikIntegration {

0 commit comments

Comments
 (0)