package checker import ( "encoding/json" "fmt" "html" "sort" "strings" "time" sdk "git.happydns.org/checker-sdk-go/checker" ) // GetHTMLReport falls back to a data-only render when the host hasn't // threaded rule states into the context yet. func (p *delegationProvider) GetHTMLReport(ctx sdk.ReportContext) (string, error) { var data DelegationData if raw := ctx.Data(); len(raw) > 0 { if err := json.Unmarshal(raw, &data); err != nil { return "", fmt.Errorf("decoding delegation data: %w", err) } } states := ctx.States() var b strings.Builder b.WriteString(``) b.WriteString(`Delegation report`) fmt.Fprintf(&b, `

Delegation of %s

`, html.EscapeString(strings.TrimSuffix(data.DelegatedFQDN, "."))) if len(states) == 0 { b.WriteString(`

No rule states were threaded into this report; rendering raw observation only.

`) writeDataOnly(&b, &data) b.WriteString(``) return b.String(), nil } writeBanner(&b, states) writeFixTheseFirst(&b, states) writeAllStates(&b, states) writeDataOnly(&b, &data) b.WriteString(``) return b.String(), nil } func (p *delegationProvider) ExtractMetrics(ctx sdk.ReportContext, collectedAt time.Time) ([]sdk.CheckMetric, error) { var data DelegationData if raw := ctx.Data(); len(raw) > 0 { if err := json.Unmarshal(raw, &data); err != nil { return nil, fmt.Errorf("decoding delegation data: %w", err) } } var metrics []sdk.CheckMetric metrics = append(metrics, sdk.CheckMetric{ Name: "delegation.parent_views.count", Value: float64(len(data.ParentViews)), Timestamp: collectedAt, }) metrics = append(metrics, sdk.CheckMetric{ Name: "delegation.child_servers.count", Value: float64(len(data.Children)), Timestamp: collectedAt, }) byRuleStatus := map[string]map[sdk.Status]int{} byStatus := map[sdk.Status]int{} for _, s := range ctx.States() { byStatus[s.Status]++ if byRuleStatus[s.RuleName] == nil { byRuleStatus[s.RuleName] = map[sdk.Status]int{} } byRuleStatus[s.RuleName][s.Status]++ } for rule, perStatus := range byRuleStatus { for status, n := range perStatus { metrics = append(metrics, sdk.CheckMetric{ Name: "delegation.rule.status", Value: float64(n), Labels: map[string]string{ "rule": rule, "status": status.String(), }, Timestamp: collectedAt, }) } } for status, n := range byStatus { if status == sdk.StatusOK { continue } metrics = append(metrics, sdk.CheckMetric{ Name: "delegation.findings.count", Value: float64(n), Labels: map[string]string{"status": status.String()}, Timestamp: collectedAt, }) } return metrics, nil } func worstStatus(states []sdk.CheckState) sdk.Status { worst := sdk.StatusOK for _, s := range states { if s.Status > worst { worst = s.Status } } return worst } func statusColor(s sdk.Status) string { switch s { case sdk.StatusOK: return "#2e7d32" case sdk.StatusInfo: return "#0277bd" case sdk.StatusWarn: return "#ef6c00" case sdk.StatusCrit: return "#c62828" case sdk.StatusError: return "#6a1b9a" default: return "#555" } } func writeBanner(b *strings.Builder, states []sdk.CheckState) { worst := worstStatus(states) fmt.Fprintf(b, `

Overall: %s

`, statusColor(worst), worst.String()) } func writeFixTheseFirst(b *strings.Builder, states []sdk.CheckState) { var fix []sdk.CheckState for _, s := range states { if s.Status >= sdk.StatusWarn { fix = append(fix, s) } } if len(fix) == 0 { return } sort.SliceStable(fix, func(i, j int) bool { if fix[i].Status != fix[j].Status { return fix[i].Status > fix[j].Status } if fix[i].RuleName != fix[j].RuleName { return fix[i].RuleName < fix[j].RuleName } return fix[i].Subject < fix[j].Subject }) b.WriteString(`

Fix these first

`) writeStatesTable(b, fix) } func writeAllStates(b *strings.Builder, states []sdk.CheckState) { sorted := append([]sdk.CheckState(nil), states...) sort.SliceStable(sorted, func(i, j int) bool { if sorted[i].RuleName != sorted[j].RuleName { return sorted[i].RuleName < sorted[j].RuleName } return sorted[i].Subject < sorted[j].Subject }) b.WriteString(`

All rule states

`) writeStatesTable(b, sorted) } func writeStatesTable(b *strings.Builder, states []sdk.CheckState) { b.WriteString(``) b.WriteString(``) for _, s := range states { fmt.Fprintf(b, ``, statusColor(s.Status), html.EscapeString(s.Status.String()), html.EscapeString(s.RuleName), html.EscapeString(s.Subject), html.EscapeString(s.Message), ) } b.WriteString(`
StatusRuleSubjectMessage
%s%s%s%s
`) } func writeDataOnly(b *strings.Builder, data *DelegationData) { b.WriteString(`

Observation

`) if data.ParentDiscoveryError != "" { fmt.Fprintf(b, `

Parent discovery error: %s

`, html.EscapeString(data.ParentDiscoveryError)) } if len(data.DeclaredNS) > 0 { fmt.Fprintf(b, `

Declared NS: %s

`, html.EscapeString(strings.Join(data.DeclaredNS, ", "))) } if len(data.DeclaredDS) > 0 { var texts []string for _, d := range data.DeclaredDS { texts = append(texts, fmt.Sprintf("keytag=%d algo=%d digest-type=%d", d.KeyTag, d.Algorithm, d.DigestType)) } fmt.Fprintf(b, `

Declared DS: %s

`, html.EscapeString(strings.Join(texts, "; "))) } if len(data.ParentViews) > 0 { b.WriteString(`

Parent views

`) } if len(data.Children) > 0 { b.WriteString(`

Delegated servers

`) } }