// Package report - Output formatters package report import ( "encoding/json" "fmt" "io" "strings" "text/template" "time" ) // Formatter formats reports for output type Formatter interface { Format(report *Report, w io.Writer) error } // JSONFormatter formats reports as JSON type JSONFormatter struct { Indent bool } // Format writes the report as JSON func (f *JSONFormatter) Format(report *Report, w io.Writer) error { var data []byte var err error if f.Indent { data, err = json.MarshalIndent(report, "", " ") } else { data, err = json.Marshal(report) } if err != nil { return err } _, err = w.Write(data) return err } // MarkdownFormatter formats reports as Markdown type MarkdownFormatter struct{} // Format writes the report as Markdown func (f *MarkdownFormatter) Format(report *Report, w io.Writer) error { tmpl := template.Must(template.New("report").Funcs(template.FuncMap{ "statusIcon": StatusIcon, "severityIcon": SeverityIcon, "formatTime": func(t time.Time) string { return t.Format("2006-01-02 15:04:05") }, "formatDate": func(t time.Time) string { return t.Format("2006-01-02") }, "upper": strings.ToUpper, }).Parse(markdownTemplate)) return tmpl.Execute(w, report) } const markdownTemplate = `# {{.Title}} **Generated:** {{formatTime .GeneratedAt}} **Period:** {{formatDate .PeriodStart}} to {{formatDate .PeriodEnd}} **Overall Status:** {{statusIcon .Status}} {{.Status}} **Compliance Score:** {{printf "%.1f" .Score}}% --- ## Executive Summary {{.Description}} | Metric | Value | |--------|-------| | Total Controls | {{.Summary.TotalControls}} | | Compliant | {{.Summary.CompliantControls}} | | Non-Compliant | {{.Summary.NonCompliantControls}} | | Partial | {{.Summary.PartialControls}} | | Compliance Rate | {{printf "%.1f" .Summary.ComplianceRate}}% | | Open Findings | {{.Summary.OpenFindings}} | | Risk Score | {{printf "%.1f" .Summary.RiskScore}} | --- ## Compliance Categories {{range .Categories}} ### {{statusIcon .Status}} {{.Name}} **Status:** {{.Status}} | **Score:** {{printf "%.1f" .Score}}% {{.Description}} | Control | Reference | Status | Notes | |---------|-----------|--------|-------| {{range .Controls}}| {{.Name}} | {{.Reference}} | {{statusIcon .Status}} | {{.Notes}} | {{end}} {{end}} --- ## Findings {{if .Findings}} | ID | Severity | Title | Status | |----|----------|-------|--------| {{range .Findings}}| {{.ID}} | {{severityIcon .Severity}} {{.Severity}} | {{.Title}} | {{.Status}} | {{end}} ### Finding Details {{range .Findings}} #### {{severityIcon .Severity}} {{.Title}} - **ID:** {{.ID}} - **Control:** {{.ControlID}} - **Severity:** {{.Severity}} - **Type:** {{.Type}} - **Status:** {{.Status}} - **Detected:** {{formatTime .DetectedAt}} **Description:** {{.Description}} **Impact:** {{.Impact}} **Recommendation:** {{.Recommendation}} --- {{end}} {{else}} No open findings. {{end}} --- ## Evidence Summary {{if .Evidence}} | ID | Type | Description | Collected | |----|------|-------------|-----------| {{range .Evidence}}| {{.ID}} | {{.Type}} | {{.Description}} | {{formatTime .CollectedAt}} | {{end}} {{else}} No evidence collected. {{end}} --- *Report generated by dbbackup compliance module* ` // HTMLFormatter formats reports as HTML type HTMLFormatter struct{} // Format writes the report as HTML func (f *HTMLFormatter) Format(report *Report, w io.Writer) error { tmpl := template.Must(template.New("report").Funcs(template.FuncMap{ "statusIcon": StatusIcon, "statusClass": statusClass, "severityIcon": SeverityIcon, "severityClass": severityClass, "formatTime": func(t time.Time) string { return t.Format("2006-01-02 15:04:05") }, "formatDate": func(t time.Time) string { return t.Format("2006-01-02") }, }).Parse(htmlTemplate)) return tmpl.Execute(w, report) } func statusClass(s ComplianceStatus) string { switch s { case StatusCompliant: return "status-compliant" case StatusNonCompliant: return "status-noncompliant" case StatusPartial: return "status-partial" default: return "status-unknown" } } func severityClass(s FindingSeverity) string { switch s { case SeverityCritical: return "severity-critical" case SeverityHigh: return "severity-high" case SeverityMedium: return "severity-medium" case SeverityLow: return "severity-low" default: return "severity-unknown" } } const htmlTemplate = ` {{.Title}}

{{.Title}}

Generated: {{formatTime .GeneratedAt}} | Period: {{formatDate .PeriodStart}} to {{formatDate .PeriodEnd}}

{{printf "%.0f" .Score}}%
{{.Status}}

{{.Description}}

{{.Summary.TotalControls}}
Total Controls
{{.Summary.CompliantControls}}
Compliant
{{.Summary.NonCompliantControls}}
Non-Compliant
{{.Summary.PartialControls}}
Partial
{{.Summary.OpenFindings}}
Open Findings
{{printf "%.1f" .Summary.RiskScore}}
Risk Score

Compliance Categories

{{range .Categories}}

{{statusIcon .Status}} {{.Name}} {{printf "%.0f" .Score}}%

{{.Description}}

{{range .Controls}} {{end}}
Control Reference Status Notes
{{.Name}} {{.Reference}} {{statusIcon .Status}} {{.Notes}}
{{end}}
{{if .Findings}}

Findings ({{len .Findings}})

{{range .Findings}}

{{severityIcon .Severity}} {{.Title}}

ID: {{.ID}} | Severity: {{.Severity}} | Status: {{.Status}} | Detected: {{formatTime .DetectedAt}}

Description: {{.Description}}

Impact: {{.Impact}}

Recommendation: {{.Recommendation}}

{{end}}
{{end}} {{if .Evidence}}

Evidence ({{len .Evidence}} items)

{{range .Evidence}} {{end}}
ID Type Description Collected
{{.ID}} {{.Type}} {{.Description}} {{formatTime .CollectedAt}}
{{end}} ` // GetFormatter returns a formatter for the given format func GetFormatter(format OutputFormat) Formatter { switch format { case FormatJSON: return &JSONFormatter{Indent: true} case FormatMarkdown: return &MarkdownFormatter{} case FormatHTML: return &HTMLFormatter{} default: return &JSONFormatter{Indent: true} } } // ConsoleFormatter formats reports for terminal output type ConsoleFormatter struct{} // Format writes the report to console func (f *ConsoleFormatter) Format(report *Report, w io.Writer) error { // Header fmt.Fprintf(w, "\n%s\n", strings.Repeat("=", 60)) fmt.Fprintf(w, " %s\n", report.Title) fmt.Fprintf(w, "%s\n\n", strings.Repeat("=", 60)) fmt.Fprintf(w, " Generated: %s\n", report.GeneratedAt.Format("2006-01-02 15:04:05")) fmt.Fprintf(w, " Period: %s to %s\n", report.PeriodStart.Format("2006-01-02"), report.PeriodEnd.Format("2006-01-02")) fmt.Fprintf(w, " Status: %s %s\n", StatusIcon(report.Status), report.Status) fmt.Fprintf(w, " Score: %.1f%%\n\n", report.Score) // Summary fmt.Fprintf(w, " SUMMARY\n") fmt.Fprintf(w, " %s\n", strings.Repeat("-", 40)) fmt.Fprintf(w, " Controls: %d total, %d compliant, %d non-compliant\n", report.Summary.TotalControls, report.Summary.CompliantControls, report.Summary.NonCompliantControls) fmt.Fprintf(w, " Compliance: %.1f%%\n", report.Summary.ComplianceRate) fmt.Fprintf(w, " Open Findings: %d (critical: %d, high: %d)\n", report.Summary.OpenFindings, report.Summary.CriticalFindings, report.Summary.HighFindings) fmt.Fprintf(w, " Risk Score: %.1f\n\n", report.Summary.RiskScore) // Categories fmt.Fprintf(w, " CATEGORIES\n") fmt.Fprintf(w, " %s\n", strings.Repeat("-", 40)) for _, cat := range report.Categories { fmt.Fprintf(w, " %s %-25s %.0f%%\n", StatusIcon(cat.Status), cat.Name, cat.Score) } fmt.Fprintln(w) // Findings if len(report.Findings) > 0 { fmt.Fprintf(w, " FINDINGS\n") fmt.Fprintf(w, " %s\n", strings.Repeat("-", 40)) for _, f := range report.Findings { fmt.Fprintf(w, " %s [%s] %s\n", SeverityIcon(f.Severity), f.Severity, f.Title) fmt.Fprintf(w, " %s\n", f.Description) } fmt.Fprintln(w) } fmt.Fprintf(w, "%s\n", strings.Repeat("=", 60)) return nil }